Configuring Firebase UI for authentication is straightforward, similar to how we set it up in the view system but with few minor changes. Let's look at how we can configure Firebase UI when using Jetpack Compose.
Gradle setup is basically following the documentation to add Firebase to the project and adding the below dependency for the UI authentication.
We will be using the following state variables, in our ProfileViewModel to perform recomposition on the composable functions based on the authentication state of the user.
AuthResultCode is an enum which has the following values
Build Login Intent
The next step would be to build our login intent, but before we do this, let's look at defining an interface which we could implement in our ProfileViewModel.
Notice that the method buildLoginActivityResult on the interface has a default implementation for the Firebase UI Activity result contract. ProfileViewModel implements the remaining two methods on the above interface.
buildLoginIntent: we create the sign in intent with our preferred sign-in methods:
For my app, I use a custom auth layout.
onLoginResult: This is where we will recieve the result once the sign-in flow is complete. I also handle linking of anonymous user data to the signed in account as well.
The above method handles the different scenarios that could happen on the sign-in result. This would than allow the composable to perform the desired actions. We will come back to this shortly once we look at our Composable function.
Define our Profile UI Composable
Since our composable root function for ProfileUI accepts ProfileViewModel, we will define our actions tied to the authentication process in this function. The child composable functions are responsible for building the UI and sending the events up to this root function.
Initialize our state variables that we configured in the viewmodel
Initialize the login launcher by using rememberLauncherForActivityResult. This registers the callback for the result, which we than use to call the onLoginResult method on our view model.
Launching FirebaseUI Sign-in Flow
Now that we have all the pieces in place, the next step would be to launch the FirebaseUI sign-in flow. This could be done depending on your app requirements. For my app, I wanted to show the login UI anytime an anonymous user selects the Profile tab in the bottom navigation.
We can use the below code to launch sign-in flow which only runs if the user is anonymous
Here LaunchedEffect is critical for the sign-in flow to run as expected
Once the flow is launched we should see the familiar FirebaseUI to perform the sign in process.
When using a custom auth layout, I noticed that the Firebase UI styles on the buttons are no longer working with MDC and I had to specify the styles
Here is how I have changed some of the styles to match the original styles. If there is a better way of doing this, I'm all ears but hoping this will be fixed in the newer versions of MDC.
Handling Login Result
In the previous section where we defined our login intent result, there is quite a bit going on there, so let's look at how we handle the outcome of each scenario.
Success: In the success state we just want to retrieve the user information and display the Profile UI with the user details. So we change the state variables in our view model so the UI can recompose. In my example, I used the setUser function to set another state variable that holds the user information.
Cancellation: When the user has cancelled the flow this would produce an empty response. We use this to update our AuthResultCode state variable and the UI can handle the desired actions. For my app, I just perform navController.popBackStack()to go back to the previous UI.
Failure: When there is an error, it could mainly either be due to a merge conflict or a network issue. If you handle merge conflict you would need to link the credential by running a suspend function and merging the user's anonymous data into the users account.
Handling merge conflict
Hope this helps you with implementing Firebase Auth UI in your app. Let me know if you have any questions on the above implementation or if there are ways to improve this code further in the comments below.