Skip to content

fix: prevent login screen flicker for authenticated users#774

Open
Arunodoy18 wants to merge 1 commit intoAOSSIE-Org:masterfrom
Arunodoy18:fix/750-auth-flicker-on-launch
Open

fix: prevent login screen flicker for authenticated users#774
Arunodoy18 wants to merge 1 commit intoAOSSIE-Org:masterfrom
Arunodoy18:fix/750-auth-flicker-on-launch

Conversation

@Arunodoy18
Copy link
Copy Markdown

@Arunodoy18 Arunodoy18 commented Feb 23, 2026

Summary

Prevents login screen flicker when an authenticated user launches the app.

Closes #750.

Changes

  • Introduces initial loading/auth resolution state.
  • Adds centralized AuthGate widget.
  • Prevents rendering LoginScreen before auth check completes.
  • Removes redirect-based navigation logic during startup.

Result

Authenticated users are now routed directly to the home screen without UI flicker.

Summary by CodeRabbit

  • Bug Fixes

    • Improved authentication state handling during app startup to prevent navigation errors when keystore properties are unavailable, with graceful fallback to debug mode.
  • New Features

    • Enhanced splash screen experience with concurrent authentication and update checks for smoother navigation flow.
    • Better handling of user authentication status detection and routing to appropriate screens based on account and profile status.

@Arunodoy18 Arunodoy18 requested a review from M4dhav as a code owner February 23, 2026 22:51
@github-actions
Copy link
Copy Markdown
Contributor

🎉 Welcome @Arunodoy18!
Thank you for your pull request! Our team will review it soon. 🔍

  • Please ensure your PR follows the contribution guidelines. ✅
  • All automated tests should pass before merging. 🔄
  • If this PR fixes an issue, link it in the description. 🔗

We appreciate your contribution! 🚀

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

Modified Android build signing configuration to conditionally fall back to debug signing when keystore properties are absent. Introduced AuthStatus enum and new navigation method to resolve authentication state during startup. Refactored splash screen to concurrently wait for auth resolution and minimum display duration before routing.

Changes

Cohort / File(s) Summary
Android Build Configuration
android/app/build.gradle.kts
Conditional signingConfigs block that only executes when keystorePropertiesFile exists; release buildType now falls back to debug signingConfig when keystore is unavailable, improving resilience to missing signing credentials.
Authentication State Management
lib/controllers/auth_state_controller.dart
Added AuthStatus enum (checking, authenticated, unauthenticated) with public Rx observable; new navigateBasedOnAuthState() method determines routing based on auth status, user profile completion, and report count; setUserProfileData updates authStatus on success/error.
Splash Screen Navigation Flow
lib/views/screens/splash_screen.dart
Replaced fixed timer with non-blocking Future.delayed; concurrently waits for 3-second minimum display and auth resolution; introduces private helper _waitForAuthResolution() using ever() observer; routes via navigateBasedOnAuthState() instead of direct AppRoutes, removing direct landing navigation dependency.

Sequence Diagram(s)

sequenceDiagram
    participant App as App Launch
    participant Splash as SplashScreen
    participant Auth as AuthStateController
    participant About as AboutAppScreenController
    participant Nav as Navigation System
    
    App->>Splash: Show Splash
    Splash->>Splash: Start concurrent timers
    Splash->>Auth: Observe authStatus (checking)
    Note over Auth: Startup auth check begins
    Splash->>Splash: Wait min 3 seconds + auth resolution
    Auth->>Auth: setUserProfileData()
    Auth->>Auth: Update authStatus (authenticated/unauthenticated)
    Splash->>Auth: _waitForAuthResolution() completes
    Splash->>About: Check for updates
    About->>About: Handle update logic
    About->>Splash: Update result
    Splash->>Auth: navigateBasedOnAuthState()
    Alt authStatus = authenticated
        Auth->>Nav: Navigate to appropriate screen<br/>(tabview, onBoarding, or userBlockedScreen)
    Else authStatus = unauthenticated
        Auth->>Nav: Navigate to landing or welcomeScreen
    End
    Nav->>App: Route to destination
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement

Poem

🐰 A splash of auth, a hop and a wait,
Where states align before it's too late,
No flickering screens, just a smooth dance,
The rabbit approves of this startup advance!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: preventing login screen flicker for authenticated users, which directly addresses the issue.
Linked Issues check ✅ Passed The changes implement auth state management, conditional signing logic, and modified splash screen flow to resolve authentication asynchronously before navigation, addressing #750's flicker issue.
Out of Scope Changes check ✅ Passed All changes align with the objective: build.gradle signing fallback enables offline builds, auth controller manages auth state, and splash screen prevents flicker by waiting for auth resolution.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@android/app/build.gradle.kts`:
- Around line 47-66: The release build currently falls back to debug signing
when keystorePropertiesFile is missing (in buildTypes -> release ->
signingConfig), which risks producing shippable artifacts signed with debug
keys; change this by making the fallback explicit or failing the build: check
keystorePropertiesFile (or a new boolean opt-in property like
allowDebugSigningForRelease) and if missing and opt-in is false throw a
GradleException to fail the release build, otherwise only use
signingConfigs.getByName("release") when the keystore exists or require the
opt-in property to be present before using the debug signing config; update the
logic around signingConfigs, keystorePropertiesFile, and the release
signingConfig assignment accordingly.

In `@lib/controllers/auth_state_controller.dart`:
- Around line 301-303: The crash happens because reportsCount (a late int set
only in setUserProfileData when isUserProfileComplete == true) can be
uninitialized when navigateBasedOnAuthState() runs; update the guard before
accessing reportsCount by short-circuiting on isUserProfileComplete (e.g. change
the condition to check isUserProfileComplete == true && reportsCount > 5) so
Get.offNamed(AppRoutes.userBlockedScreen) is only evaluated when the profile is
complete and reportsCount is initialized.

In `@lib/views/screens/splash_screen.dart`:
- Around line 89-108: The _waitForAuthResolution() routine can miss a state flip
that occurs between the initial check and the ever() subscription; update it so
that after attaching the worker (ever on authController.authStatus) you
immediately re-check authController.authStatus and complete the Completer if
it's no longer AuthStatus.checking, then dispose the worker; ensure you still
guard with !completer.isCompleted to avoid double-completion.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf1dbe2 and e037cbc.

📒 Files selected for processing (3)
  • android/app/build.gradle.kts
  • lib/controllers/auth_state_controller.dart
  • lib/views/screens/splash_screen.dart

Comment on lines +47 to +66
if (keystorePropertiesFile.exists()) {
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = keystoreProperties["storeFile"]?.let { file(it) }
storePassword = keystoreProperties["storePassword"] as String
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = keystoreProperties["storeFile"]?.let { file(it) }
storePassword = keystoreProperties["storePassword"] as String
}
}
}

buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
isMinifyEnabled = false
isShrinkResources = false
signingConfig = signingConfigs.getByName("debug")
signingConfig = signingConfigs.getByName("release")
signingConfig = if (keystorePropertiesFile.exists()) {
signingConfigs.getByName("release")
} else {
signingConfigs.getByName("debug")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid auto-falling back to debug signing for release builds.

This can generate a shippable release artifact signed with debug keys, which is a security and compliance risk. Gate the fallback behind an explicit opt‑in or fail the release build when the keystore is missing.

🔒 Suggested guard for release signing
 release {
   isMinifyEnabled = false
   isShrinkResources = false
-  signingConfig = if (keystorePropertiesFile.exists()) {
-    signingConfigs.getByName("release")
-  } else {
-    signingConfigs.getByName("debug")
-  }
+  signingConfig = when {
+    keystorePropertiesFile.exists() ->
+      signingConfigs.getByName("release")
+    project.hasProperty("allowDebugSigning") ->
+      signingConfigs.getByName("debug")
+    else ->
+      throw GradleException("Missing key.properties for release signing")
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/app/build.gradle.kts` around lines 47 - 66, The release build
currently falls back to debug signing when keystorePropertiesFile is missing (in
buildTypes -> release -> signingConfig), which risks producing shippable
artifacts signed with debug keys; change this by making the fallback explicit or
failing the build: check keystorePropertiesFile (or a new boolean opt-in
property like allowDebugSigningForRelease) and if missing and opt-in is false
throw a GradleException to fail the release build, otherwise only use
signingConfigs.getByName("release") when the keystore exists or require the
opt-in property to be present before using the debug signing config; update the
logic around signingConfigs, keystorePropertiesFile, and the release
signingConfig assignment accordingly.

Comment on lines +301 to +303
if (reportsCount > 5) {
Get.offNamed(AppRoutes.userBlockedScreen);
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find the file and check if it exists
fd -t f auth_state_controller.dart

Repository: AOSSIE-Org/Resonate

Length of output: 105


🏁 Script executed:

# Get the file size to see if we can read it
wc -l lib/controllers/auth_state_controller.dart

Repository: AOSSIE-Org/Resonate

Length of output: 109


🏁 Script executed:

# Read the relevant lines around 301-303 with broader context
sed -n '280,320p' lib/controllers/auth_state_controller.dart

Repository: AOSSIE-Org/Resonate

Length of output: 1652


🏁 Script executed:

# Search for reportsCount declaration and all its usages
rg "reportsCount" lib/controllers/auth_state_controller.dart -n

Repository: AOSSIE-Org/Resonate

Length of output: 185


🏁 Script executed:

# Search for isUserProfileComplete to understand the initialization pattern
rg "isUserProfileComplete" lib/controllers/auth_state_controller.dart -n -A 2 -B 2

Repository: AOSSIE-Org/Resonate

Length of output: 884


🏁 Script executed:

# Get context around reportsCount assignment at line 229
sed -n '200,260p' lib/controllers/auth_state_controller.dart

Repository: AOSSIE-Org/Resonate

Length of output: 2227


🏁 Script executed:

# Get the full navigateBasedOnAuthState() method context
sed -n '276,315p' lib/controllers/auth_state_controller.dart

Repository: AOSSIE-Org/Resonate

Length of output: 1786


Guard reportsCount access to prevent LateInitializationError for incomplete profiles.

reportsCount is a late int field that is only initialized inside the if (isUserProfileComplete == true) block in setUserProfileData(). In navigateBasedOnAuthState(), if authStatus is already authenticated and isUserProfileComplete == false, the field remains uninitialized when accessed at line 301, causing a crash during navigation.

The fix uses short-circuit evaluation to prevent the access when the profile is incomplete:

Guard before accessing reportsCount
-      if (reportsCount > 5) {
+      if (isUserProfileComplete == true && reportsCount > 5) {
         Get.offNamed(AppRoutes.userBlockedScreen);
         return;
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/controllers/auth_state_controller.dart` around lines 301 - 303, The crash
happens because reportsCount (a late int set only in setUserProfileData when
isUserProfileComplete == true) can be uninitialized when
navigateBasedOnAuthState() runs; update the guard before accessing reportsCount
by short-circuiting on isUserProfileComplete (e.g. change the condition to check
isUserProfileComplete == true && reportsCount > 5) so
Get.offNamed(AppRoutes.userBlockedScreen) is only evaluated when the profile is
complete and reportsCount is initialized.

Comment on lines +89 to 108
/// Returns a [Future] that completes as soon as [AuthStateController.authStatus]
/// leaves [AuthStatus.checking].
///
/// If the auth check is already resolved (fast local session cache), this
/// completes on the very next microtask so no extra delay is introduced.
Future<void> _waitForAuthResolution() async {
if (authController.authStatus.value != AuthStatus.checking) return;

final completer = Completer<void>();
final worker = ever<AuthStatus>(
authController.authStatus,
(AuthStatus status) {
if (status != AuthStatus.checking && !completer.isCompleted) {
completer.complete();
}
},
);
await completer.future;
worker.dispose();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

GetX ever() function behavior immediate callback current value

💡 Result:

ever(rx, callback) does not invoke callback immediately when you register it; it only runs when the observed Rx emits an event (i.e., a change/assignment that triggers a notify). (chornthorn.github.io)

One GetX nuance: by default, the first assignment after an Rx is created can still emit an event even if the value is the same (to handle cases like false.obs where a “load” might set false again). You can disable that with rx.firstRebuild = false;. (pub.dev)

If you need an “immediate callback with the current value”, call it yourself right after wiring ever, e.g. callback(rx.value), then rely on ever for subsequent emissions.

Citations:


🏁 Script executed:

# Check the actual implementation in the codebase
cd lib/views/screens && cat -n splash_screen.dart | sed -n '89,108p'

Repository: AOSSIE-Org/Resonate

Length of output: 924


🏁 Script executed:

# Search for other ever() usages in codebase to understand patterns
rg "ever<" --type dart -B 2 -A 3

Repository: AOSSIE-Org/Resonate

Length of output: 495


Close the race where authStatus flips before ever() attaches.

GetX's ever() only fires on value changes, not on the current value. If authStatus changes after the initial check (line 95) but before ever() subscribes (line 98), that state transition is missed and the completer may never resolve. Add an immediate post-subscribe check to handle this window:

🛠️ Safer completion with a post-subscribe check
   final completer = Completer<void>();
   final worker = ever<AuthStatus>(
     authController.authStatus,
     (AuthStatus status) {
       if (status != AuthStatus.checking && !completer.isCompleted) {
         completer.complete();
       }
     },
   );
+  if (authController.authStatus.value != AuthStatus.checking &&
+      !completer.isCompleted) {
+    completer.complete();
+  }
   await completer.future;
   worker.dispose();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/views/screens/splash_screen.dart` around lines 89 - 108, The
_waitForAuthResolution() routine can miss a state flip that occurs between the
initial check and the ever() subscription; update it so that after attaching the
worker (ever on authController.authStatus) you immediately re-check
authController.authStatus and complete the Completer if it's no longer
AuthStatus.checking, then dispose the worker; ensure you still guard with
!completer.isCompleted to avoid double-completion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Login screen briefly appears for already authenticated users on app launch

1 participant