I am attempting to get a user to sign in using their Google account. The android API I am using for to achieve this is the Activity Results API.
When a user clicks the Google button the intent will fire and they're able to select an account to sign-in with. However, the result code of the intent is always RESULT_CANCELLED.
The code below is my attempt.
build.gradle.kts
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = ""
compileSdk = 33
defaultConfig {
applicationId = ""
minSdk = 24
targetSdk = 33
versionCode = 3
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders["appAuthRedirectScheme"] = "SCHEME_TODO"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.3.2"
}
}
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.5.1")
implementation("com.google.android.material:material:1.6.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("com.google.android.gms:play-services-auth:20.3.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
// Coil for image loading
implementation("io.coil-kt:coil-compose:2.2.2")
val composeBom = platform("androidx.compose:compose-bom:2022.10.00")
implementation(composeBom)
androidTestImplementation(composeBom)
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui-tooling-preview")
debugImplementation("androidx.compose.ui:ui-tooling")
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-test-manifest")
implementation("androidx.compose.material3:material3-window-size-class")
implementation("androidx.activity:activity-compose:1.5.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1")
implementation("androidx.compose.runtime:runtime-livedata")
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val GCP_ID = "****************"
private val TAG = this.javaClass.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val googleLauncher = getStartForResult(
onSuccess = {
Toast.makeText(this, it.toString(), Toast.LENGTH_SHORT).show()
},
onFailure = {
Toast.makeText(this, it.toString(), Toast.LENGTH_SHORT).show()
})
setContent {
LoginScreen(onGoogleClick = {
googleLauncher.launch(getGoogleLoginAuth(this).signInIntent)
})
}
}
private fun getStartForResult(
onSuccess: (ContinueWithGoogleResult) -> Unit,
onFailure: (Exception?) -> Unit
): ActivityResultLauncher<Intent> {
return registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
val intent = it.data
if (it.data != null) {
val task = GoogleSignIn.getSignedInAccountFromIntent(intent)
handleSignInResult(task, onSuccess, onFailure)
} else {
Log.e(TAG, "Intent data for sign-in request was unexpectedly null.")
}
} else {
Log.e(TAG, "Sign-in request was unexpectedly cancelled.")
}
}
}
private fun getGoogleLoginAuth(activity: Activity): GoogleSignInClient =
GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN).run {
requestEmail()
requestIdToken(GCP_ID)
requestId()
requestProfile()
GoogleSignIn.getClient(activity, build())
}
private fun handleSignInResult(
task: Task<GoogleSignInAccount>,
onSuccess: (ContinueWithGoogleResult) -> Unit,
onFailure: (Exception?) -> Unit
) {
task.addOnCompleteListener {
if (it.isSuccessful) {
val acc = task.result
val res = ContinueWithGoogleResult(
acc.email,
acc.displayName,
acc.familyName,
acc.givenName,
acc.id,
acc.photoUrl,
acc.isExpired
)
onSuccess(res)
} else {
val ex = task.exception
onFailure(ex)
}
}
}
data class ContinueWithGoogleResult(
val email: String?,
val displayName: String?,
val familyName: String?,
val givenName: String?,
val id: String?,
val photoUrl: Uri?,
val isExpired: Boolean?
)
@Composable
fun LoginScreen(
onGoogleClick: () -> Unit,
modifier: Modifier = Modifier
) {
Button(
onClick = {
onGoogleClick()
},
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp),
shape = RoundedCornerShape(6.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Black,
contentColor = Color.White
)
) {
Image(
painter = rememberAsyncImagePainter(
model = ContextCompat.getDrawable(
this@MainActivity,
R.drawable.btn_google_light_normal_hdpi
)
),
contentDescription = ""
)
Text(text = "Sign in with Google", modifier = Modifier.padding(6.dp))
}
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApp"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
</application>
</manifest>