Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Build with Gradle
run: ./scripts/build.sh
- name: Print Logs
if: failure()
run: ./scripts/print_build_logs.sh
- uses: actions/checkout@v2
- name: set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 17
- name: Build with Gradle
run: ./scripts/build.sh
- name: Print Logs
if: failure()
run: ./scripts/print_build_logs.sh
7 changes: 2 additions & 5 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ android {
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

buildFeatures {
Expand Down Expand Up @@ -95,9 +95,6 @@ dependencies {
implementation(Config.Libs.Misc.permissions)
implementation(Config.Libs.Androidx.constraint)
debugImplementation(Config.Libs.Misc.leakCanary)
debugImplementation(Config.Libs.Misc.leakCanaryFragments)
releaseImplementation(Config.Libs.Misc.leakCanaryNoop)
testImplementation(Config.Libs.Misc.leakCanaryNoop)
}

apply(plugin = "com.google.gms.google-services")
13 changes: 0 additions & 13 deletions app/src/main/java/com/firebase/uidemo/FirebaseUIDemo.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,10 @@
package com.firebase.uidemo;

import com.squareup.leakcanary.LeakCanary;

import androidx.appcompat.app.AppCompatDelegate;
import androidx.multidex.MultiDexApplication;

public class FirebaseUIDemo extends MultiDexApplication {
static {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
}

@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
}
}
16 changes: 12 additions & 4 deletions auth/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import com.android.build.gradle.internal.dsl.TestOptions

plugins {
id("com.android.library")
id("com.vanniktech.maven.publish")
id("com.android.library")
id("com.vanniktech.maven.publish")
id("org.jetbrains.kotlin.android")
}

android {
Expand All @@ -26,8 +27,8 @@ android {
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

lint {
Expand Down Expand Up @@ -62,6 +63,9 @@ android {
isIncludeAndroidResources = true
}
}
kotlinOptions {
jvmTarget = "17"
}
}

dependencies {
Expand All @@ -72,8 +76,12 @@ dependencies {
implementation(Config.Libs.Androidx.fragment)
implementation(Config.Libs.Androidx.customTabs)
implementation(Config.Libs.Androidx.constraint)
implementation("androidx.credentials:credentials:1.3.0")
implementation("androidx.core:core-ktx:1.10.1")

implementation(Config.Libs.Androidx.lifecycleExtensions)
implementation("androidx.core:core-ktx:1.13.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0")
annotationProcessor(Config.Libs.Androidx.lifecycleCompiler)

implementation(platform(Config.Libs.Firebase.bom))
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.firebase.ui.auth.ui.credentials

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import com.firebase.ui.auth.IdpResponse
import com.firebase.ui.auth.data.model.FlowParameters
import com.firebase.ui.auth.data.model.Resource
import com.firebase.ui.auth.ui.InvisibleActivityBase
import com.firebase.ui.auth.util.ExtraConstants
import com.firebase.ui.auth.viewmodel.ResourceObserver
import com.firebase.ui.auth.viewmodel.credentialmanager.CredentialManagerHandler
import com.google.android.gms.auth.api.credentials.Credential
import androidx.lifecycle.ViewModelProvider
import com.google.firebase.auth.FirebaseAuth

class CredentialSaveActivity : InvisibleActivityBase() {

companion object {
private const val TAG = "CredentialSaveActivity"

@JvmStatic
fun createIntent(
context: Context,
flowParams: FlowParameters,
credential: Credential,
response: IdpResponse
): Intent {
return createBaseIntent(context, CredentialSaveActivity::class.java, flowParams).apply {
putExtra(ExtraConstants.CREDENTIAL, credential)
putExtra(ExtraConstants.IDP_RESPONSE, response)
}
}
}

private lateinit var credentialManagerHandler: CredentialManagerHandler

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val response: IdpResponse? = intent.getParcelableExtra(ExtraConstants.IDP_RESPONSE)
val credential: Credential? = intent.getParcelableExtra(ExtraConstants.CREDENTIAL)

credentialManagerHandler = ViewModelProvider(this)
.get(CredentialManagerHandler::class.java)
.apply {
// Initialize with flow parameters
init(flowParams)
// If we have an IdpResponse, set it so subsequent operations can report results
response?.let { setResponse(it) }

// Observe the operation resource
operation.observe(
this@CredentialSaveActivity,
object : ResourceObserver<IdpResponse>(this@CredentialSaveActivity) {
override fun onSuccess(response: IdpResponse) {
// Done saving – success
finish(RESULT_OK, response.toIntent())
}

override fun onFailure(e: Exception) {
// We don’t want to block the sign-in flow just because saving failed,
// so return RESULT_OK
response?.let {
finish(RESULT_OK, it.toIntent())
} ?: finish(RESULT_OK, null)
}
}
)
}

val currentOp: Resource<IdpResponse>? = credentialManagerHandler.operation.value

if (currentOp == null) {
Log.d(TAG, "Launching save operation.")
// In the old SmartLock flow, you saved a `Credential`;
// with CredentialManager, we typically need email & password for the new request.
// Example usage: pass the current user & the password.
// Adjust as needed for passkeys or other flows.
val firebaseUser = FirebaseAuth.getInstance().currentUser
val password = credential?.password

credentialManagerHandler.saveCredentials(this, firebaseUser, password)
} else {
Log.d(TAG, "Save operation in progress, doing nothing.")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.firebase.ui.auth.viewmodel.credentialmanager

import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.credentials.CreatePasswordRequest
import androidx.credentials.CredentialManager
import androidx.credentials.exceptions.CreateCredentialException
import com.firebase.ui.auth.ErrorCodes
import com.firebase.ui.auth.FirebaseUiException
import com.firebase.ui.auth.IdpResponse
import com.firebase.ui.auth.data.model.Resource
import com.firebase.ui.auth.viewmodel.AuthViewModelBase
import com.google.firebase.auth.FirebaseUser
import kotlinx.coroutines.launch

class CredentialManagerHandler(application: Application) :
AuthViewModelBase<IdpResponse>(application) {

private val credentialManager = CredentialManager.create(application)
private var response: IdpResponse? = null

fun setResponse(newResponse: IdpResponse) {
response = newResponse
}

/**
* Saves credentials via Credential Manager if enabled in [getArguments().enableCredentials].
* Uses a password-based credential for demonstration; adapt to passkeys or other flows as needed.
*/
fun saveCredentials(
activity: androidx.activity.ComponentActivity,
firebaseUser: FirebaseUser?,
password: String?
) {
if (!arguments.enableCredentials) {
setResult(Resource.forSuccess(response!!))
return
}
setResult(Resource.forLoading())

if (firebaseUser == null || firebaseUser.email.isNullOrEmpty() || password.isNullOrEmpty()) {
setResult(
Resource.forFailure(
FirebaseUiException(
ErrorCodes.UNKNOWN_ERROR,
"Invalid FirebaseUser or missing password."
)
)
)
return
}

// Example: Password credential with the user's email as the identifier
val request = CreatePasswordRequest(
id = firebaseUser.email!!,
password = password
)

viewModelScope.launch {
try {
credentialManager.createCredential(activity, request)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like createCredential() takes Context as argument. Maybe we can pass that to saveCredentials() instead of activity: androidx.activity.ComponentActivity ?

Also, do we care about the result of createCredential() ? it seems like we're ignoring it right now.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I understood, we don't need to get the result - if createCredential() succeeds, we've safely saved the user's password. But that brings me to another question: (out of curiosity) what's in the result of this call?

setResult(Resource.forSuccess(response!!))
} catch (e: CreateCredentialException) {
setResult(
Resource.forFailure(
FirebaseUiException(
ErrorCodes.UNKNOWN_ERROR,
"Error saving credential with Credential Manager.",
e
)
)
)
} catch (e: Exception) {
setResult(
Resource.forFailure(
FirebaseUiException(
ErrorCodes.UNKNOWN_ERROR,
"Unexpected error saving credential.",
e
)
)
)
}
}
}
}
Loading
Loading