feat(mobile): onboarding and Firebase authentication - #43
Conversation
- Navigation Compose NavHost with three routes: onboarding, auth (login/register), and home; start destination is resolved by combining DataStore onboarding flag with Firebase auth state so the app routes correctly on every cold start - AuthRepository interface + FirebaseAuthRepository: signInWithEmail, registerWithEmail (creates user + sets displayName), signInWithGoogle via Credential Manager API, and signOut - AuthViewModel exposes AuthUiState (Idle / Loading / Success / Error), currentUser StateFlow, and clearError(); uses ViewModel factory pattern for manual injection (no DI framework) - AppViewModel combines authStateFlow + hasSeenOnboarding into a single StartDestination StateFlow (null while initialising); nav graph waits until resolved before showing anything - OnboardingScreen with "Get Started" CTA; completion writes to DataStore via DataStoreOnboardingRepository so it is only shown once - LoginScreen: email + password fields, Google sign-in button, error Snackbar, link to Register - RegisterScreen: name / email / password / confirm-password fields with client-side password-match guard, error Snackbar, link to Login - HomeScreen: shows displayName (falls back to email), sign-out button; displayName is derived from FirebaseUser in AppNavGraph so the screen stays logic-free - 39 unit tests (AuthViewModel, OnboardingViewModel, AppViewModel) using fake repository stubs; 18 instrumented test stubs for all screens - Dependencies added: navigation-compose 2.9.0, firebase-auth-ktx (BOM), credentials 1.5.0 + credentials-play-services-auth, googleid 1.1.1, datastore-preferences 1.1.7, lifecycle-viewmodel-compose 2.9.0
|
Warning Review limit reached
More reviews will be available in 3 minutes. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (18)
📝 WalkthroughWalkthroughAdds a complete authentication and onboarding flow to the Android mobile template. Introduces ChangesAuth, Onboarding & Navigation Flow
Sequence Diagram(s)sequenceDiagram
actor User
participant MainActivity
participant AppViewModel
participant AppNavGraph
participant AuthViewModel
participant FirebaseAuthRepository
participant FirebaseAuth
participant CredentialManager
MainActivity->>AppViewModel: initialize(authRepo, onboardingRepo)
AppViewModel-->>AppNavGraph: startDestination = ONBOARDING | LOGIN | HOME
rect rgba(70, 130, 180, 0.5)
Note over User, AppNavGraph: Email Sign-In Flow
User->>AppNavGraph: enters email + password, taps Sign In
AppNavGraph->>AuthViewModel: signIn(email, password)
AuthViewModel->>FirebaseAuthRepository: signInWithEmail(email, password)
FirebaseAuthRepository->>FirebaseAuth: signInWithEmailAndPassword().await()
FirebaseAuth-->>FirebaseAuthRepository: Result<Unit>
FirebaseAuthRepository-->>AuthViewModel: Result<Unit>
AuthViewModel-->>AppNavGraph: uiState = Success
AppNavGraph->>AppNavGraph: LaunchedEffect → navigate to HOME, clear back stack
end
rect rgba(60, 179, 113, 0.5)
Note over User, AppNavGraph: Google Sign-In Flow
User->>AppNavGraph: taps Google Sign-In
AppNavGraph->>AuthViewModel: signInWithGoogle(activity)
AuthViewModel->>FirebaseAuthRepository: signInWithGoogle(activity)
FirebaseAuthRepository->>CredentialManager: getCredential(GetGoogleIdOption)
CredentialManager-->>FirebaseAuthRepository: GoogleIdTokenCredential
FirebaseAuthRepository->>FirebaseAuth: signInWithCredential(GoogleAuthProvider)
FirebaseAuth-->>FirebaseAuthRepository: Result<Unit>
FirebaseAuthRepository-->>AuthViewModel: Result<Unit>
AuthViewModel-->>AppNavGraph: uiState = Success → navigate to HOME
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- Add "Continue with Google" OutlinedButton with inline Google G logo (four-colour vector, no resource file required) above the email/password fields with an OR divider - Wire onSignInWithGoogle callback through AppNavGraph; activity reference obtained from LocalContext so LoginScreen stays activity-independent - Add onSignInWithGoogle parameter to LoginScreen and update all call sites and previews - Add instrumented tests: button visibility, click callback, existing tests updated with new parameter
There was a problem hiding this comment.
Actionable comments posted: 14
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@mobile/app/build.gradle.kts`:
- Line 31: The GOOGLE_WEB_CLIENT_ID buildConfigField currently falls back to an
empty string, which lets the app compile with a broken auth configuration.
Update the Gradle configuration in build.gradle.kts to validate the property
before using it, and fail the build during configuration if the value is missing
or blank, especially for release variants. Use the existing localProps lookup
and the GOOGLE_WEB_CLIENT_ID buildConfigField definition as the entry points for
the change.
In
`@mobile/app/src/androidTest/java/com/company/template/auth/LoginScreenTest.kt`:
- Around line 23-35: The LoginScreen test is using onNodeWithText("Sign In"),
which is ambiguous because it matches both the heading and the CTA button.
Update the test in LoginScreenTest to target the sign-in button by semantics
instead of raw text, using a button-specific matcher or a dedicated test tag on
the CTA before asserting or performing click actions.
In `@mobile/app/src/androidTest/java/com/company/template/home/HomeScreenTest.kt`:
- Around line 62-71: The HomeScreen empty-display-name test still only verifies
the heading, so it can pass even if the name text is rendered; update
homeScreen_emptyDisplayName_doesNotShowNameText in HomeScreenTest to explicitly
assert that a non-empty name like "Alice" does not exist when HomeScreen is
composed with an empty displayName. Keep the existing heading check if needed,
but add the missing negative assertion against the name text using the Compose
test rule.
In `@mobile/app/src/main/java/com/company/template/auth/AuthRepository.kt`:
- Around line 7-12: Move Activity handling out of the auth boundary by changing
AuthRepository.signInWithGoogle and the corresponding AuthViewModel flow so the
auth layer accepts a UI-produced credential/token result instead of
android.app.Activity. Keep the Activity usage in the UI layer, update the
ViewModel to receive the Google sign-in result and forward only the credential
data to the repository, and adjust any implementations/tests that currently
depend on Activity.
In `@mobile/app/src/main/java/com/company/template/auth/AuthViewModel.kt`:
- Around line 59-62: The sign-out flow in AuthViewModel.signOut() only calls
repo.signOut() and leaves the auth screen state unchanged, so reset the
ViewModel’s auth uiState back to a non-success state as part of sign-out. Update
the signOut coroutine to clear AuthUiState.Success before or after
repo.signOut(), using the existing uiState state holder in AuthViewModel so
AppNavGraph no longer treats the user as authenticated after logout.
In
`@mobile/app/src/main/java/com/company/template/auth/FirebaseAuthRepository.kt`:
- Around line 95-97: signOut() currently only calls FirebaseAuth.signOut(), so
the previous Google credential can still be reused. Update
FirebaseAuthRepository.signOut() to also clear Credential Manager state by
calling CredentialManager.clearCredentialState(...) as part of the logout flow,
and thread in the needed Context or CredentialManager dependency through this
path so the cleanup can happen when signing out.
In `@mobile/app/src/main/java/com/company/template/auth/LoginScreen.kt`:
- Around line 39-72: The LoginScreen composable is holding mutable form state
and clearing errors inline, which violates the rule that UI functions stay pure.
Move email and password state out of LoginScreen into the ViewModel or parent
caller, and drive both fields via state plus explicit events instead of
rememberSaveable and direct mutation. Also replace the inline onClearError()
calls in the OutlinedTextField onValueChange handlers with a dispatched UI event
handled outside the composable, using LoginScreen and the AuthUiState flow as
the main touchpoints.
- Around line 32-38: The LoginScreen UI currently only exposes email/password
sign-in and register navigation, so the Google auth flow is unreachable from
this screen. Update LoginScreen to accept a dedicated Google sign-in callback,
add a clear CTA that invokes it, and wire it through the existing screen
composable alongside onSignIn and onNavigateToRegister. Also update the
instrumented test coverage for LoginScreen to assert the new Google sign-in
action is rendered and triggers the new callback.
In `@mobile/app/src/main/java/com/company/template/auth/RegisterScreen.kt`:
- Around line 41-123: RegisterScreen is owning form state with rememberSaveable
and mutating error state inside the Composable, which violates the rule that UI
functions stay pure. Move name, email, password, and confirmPassword state out
of RegisterScreen into the ViewModel or caller, and pass the current values plus
change callbacks down as parameters. Also remove the in-field onClearError side
effects from the OutlinedTextField onValueChange handlers and let the
caller/ViewModel handle error clearing and submission logic.
In `@mobile/app/src/main/java/com/company/template/navigation/AppNavGraph.kt`:
- Around line 57-63: The onboarding flow only navigates in the OnboardingScreen
onGetStarted callback and never writes the seen flag, so persist it first by
calling OnboardingRepository.markSeen() from the ViewModel or the calling
composable before navigation, then navigate to ROUTE_LOGIN. Keep the composable
free of business logic by hoisting this work out of AppNavGraph/OnboardingScreen
and wiring the callback to a state/action in AppViewModel or its caller.
In
`@mobile/app/src/main/java/com/company/template/onboarding/DataStoreOnboardingRepository.kt`:
- Around line 18-19: The hasSeenOnboarding() flow in
DataStoreOnboardingRepository should handle DataStore read failures before
mapping, because context.dataStore.data can throw IOException and break
onboarding state resolution. Update the flow pipeline in hasSeenOnboarding() to
catch read errors on the data source, emit emptyPreferences() when an
IOException occurs, and then continue with the existing map logic so the default
false value is still returned safely.
In
`@mobile/app/src/main/java/com/company/template/onboarding/OnboardingViewModel.kt`:
- Around line 15-19: The OnboardingViewModel.markSeen coroutine currently lets
repo.markSeen() throw and skip onComplete(), so update markSeen to surface
failures instead of silently failing. Wrap the repo.markSeen() call in error
handling and either add an explicit error callback parameter or return/report
the exception so callers can react, while keeping the existing success path that
invokes onComplete().
In
`@mobile/app/src/test/java/com/company/template/navigation/AppViewModelTest.kt`:
- Around line 105-116: The `startDestination is Home when user is signed in`
test does not cover the signed-in branch because it sets
`fakeAuth.setUser(null)` and only asserts `StartDestination.LOGIN`, duplicating
the null-user case. Update this test in `AppViewModelTest` to exercise the
non-null auth path by configuring `fakeAuth` to emit a signed-in user state
before calling `createViewModel()`, then assert that
`viewModel.startDestination` resolves to `StartDestination.HOME` while keeping
the existing onboarding setup.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 31342138-d931-4bf0-ac16-c4de07020620
📒 Files selected for processing (22)
mobile/app/build.gradle.ktsmobile/app/src/androidTest/java/com/company/template/auth/LoginScreenTest.ktmobile/app/src/androidTest/java/com/company/template/auth/RegisterScreenTest.ktmobile/app/src/androidTest/java/com/company/template/home/HomeScreenTest.ktmobile/app/src/androidTest/java/com/company/template/onboarding/OnboardingScreenTest.ktmobile/app/src/main/java/com/company/template/MainActivity.ktmobile/app/src/main/java/com/company/template/auth/AuthRepository.ktmobile/app/src/main/java/com/company/template/auth/AuthViewModel.ktmobile/app/src/main/java/com/company/template/auth/FirebaseAuthRepository.ktmobile/app/src/main/java/com/company/template/auth/LoginScreen.ktmobile/app/src/main/java/com/company/template/auth/RegisterScreen.ktmobile/app/src/main/java/com/company/template/home/HomeScreen.ktmobile/app/src/main/java/com/company/template/navigation/AppNavGraph.ktmobile/app/src/main/java/com/company/template/navigation/AppViewModel.ktmobile/app/src/main/java/com/company/template/onboarding/DataStoreOnboardingRepository.ktmobile/app/src/main/java/com/company/template/onboarding/OnboardingRepository.ktmobile/app/src/main/java/com/company/template/onboarding/OnboardingScreen.ktmobile/app/src/main/java/com/company/template/onboarding/OnboardingViewModel.ktmobile/app/src/test/java/com/company/template/auth/AuthViewModelTest.ktmobile/app/src/test/java/com/company/template/navigation/AppViewModelTest.ktmobile/app/src/test/java/com/company/template/onboarding/OnboardingViewModelTest.ktmobile/gradle/libs.versions.toml
…s.json - Pass applicationContext into FirebaseAuthRepository so it can read the default_web_client_id string resource generated by the google-services plugin from google-services.json - resolveWebClientId() tries the generated resource first, then falls back to BuildConfig.GOOGLE_WEB_CLIENT_ID (local.properties) - Handle NoCredentialException by launching Settings.ACTION_ADD_ACCOUNT so users with no Google account on device are guided to add one - GOOGLE_WEB_CLIENT_ID retained in BuildConfig as fallback for environments where google-services.json lacks the web OAuth client
GetGoogleIdOption uses the One Tap UX which gets suppressed by Google based on authorization history and dismissal count, causing NoCredentialException even when a Google account exists on the device. GetSignInWithGoogleOption is designed for explicit button-triggered sign-in flows and always shows the full account picker without suppression or filtering by prior authorization status.
- Introduce User domain type so authStateFlow emits User? instead of FirebaseUser?, enabling JVM-only tests without Android framework deps - Remove Activity from AuthRepository interface; move CredentialManager and Google token fetching into AuthViewModel.signInWithGoogle() - FirebaseAuthRepository.signOut() now clears CredentialManager state to prevent stale Google account reuse on next sign-in - AuthViewModel.signOut() resets _uiState to Idle (was leaving AuthUiState.Success latched, causing nav loop back to Home) - Hoist LoginScreen/RegisterScreen form state to AuthViewModel via LoginFormState/RegisterFormState; screens receive state + callbacks - AppNavGraph calls appViewModel.markOnboardingSeen() before navigating away from onboarding so sign-out does not re-show onboarding - AppViewModel.markOnboardingSeen() added for nav graph to call - DataStoreOnboardingRepository.hasSeenOnboarding() catches IOException and emits emptyPreferences() as a safe fallback - OnboardingViewModel.markSeen() uses runCatching so onComplete is not silently skipped on DataStore failure - build.gradle.kts: release assembleRelease/bundleRelease tasks fail fast if GOOGLE_WEB_CLIENT_ID is absent from local.properties - Add LoginTestTags.SIGN_IN_BUTTON and RegisterTestTags.CREATE_ACCOUNT_BUTTON test tags to disambiguate nodes that share text with headings - Fix AppViewModelTest HOME branch: was setting user=null and asserting LOGIN — now sets a real User object and asserts HOME - HomeScreenTest: assert empty displayName does not render a name node Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Part of #37
Summary
AppNavGraph(Navigation Compose) with three routes —onboarding,login/register,home; start destination is computed by combining DataStore onboarding flag with Firebase auth state, so the app always routes correctly on cold start without a splash screen delayAuthRepositoryinterface +FirebaseAuthRepositoryimplementing email/password sign-in, registration (withdisplayNameset viaupdateProfile), Google Sign-In via Credential Manager API, and sign-outDataStoreOnboardingRepository; completion is persisted so it only shows on first launchAuthViewModel(Idle/Loading/Success/Error +currentUserStateFlow) andAppViewModel(resolves start destination); both use ViewModel factory pattern for manual injectionfirebase-auth-ktx(BOM), Credential Manager 1.5.0,googleid1.1.1, DataStore Preferences 1.1.7,lifecycle-viewmodel-compose2.9.0Architecture
Follows the same Firebase-first pattern as the web app:
FirebaseUser.getIdToken()for backend API callsTest plan
GOOGLE_WEB_CLIENT_ID=<your-web-oauth-client-id>tomobile/local.properties(from Firebase Console → Authentication → Google → Web SDK configuration)./gradlew assembleDebug— builds cleanly./gradlew lint && ./gradlew test— 0 issues, 39 unit tests passNotes
connectedAndroidTest(instrumented tests) require a connected emulator — compile-verified but not run in CI yetGreetingcomposable inMainActivityis retained for the existing@Preview— it can be removed in a follow-up cleanupSummary by CodeRabbit
New Features
Bug Fixes