Skip to content

Fix: FlutterError on Snackbar Overlay#547

Open
SantamRC wants to merge 2 commits intoCircuitVerse:masterfrom
SantamRC:snackbar-error
Open

Fix: FlutterError on Snackbar Overlay#547
SantamRC wants to merge 2 commits intoCircuitVerse:masterfrom
SantamRC:snackbar-error

Conversation

@SantamRC
Copy link
Copy Markdown

@SantamRC SantamRC commented Mar 23, 2026

Fixes #

This fixes the FlutterError on Snackbar Overlay issue #545

Login issue: the snackbar was trying to show before the app's overlay (the layer that displays floating UI like snackbars) was fully ready. Fixed by wrapping the call in a Future.microtask so it waits for the overlay to be mounted.

Logout issue: the snackbar was trying to show after the overlay was already destroyed. When logout fires, it clears the user session and triggers a navigation change that tears down the overlay — and GetX's snackbar queue runs too late, finding nothing to attach to. Fixed by capturing the ScaffoldMessengerState before logout starts, since it lives higher in the widget tree and survives navigation changes.

Summary by CodeRabbit

Release Notes

  • New Features

    • Logout operations now provide immediate user feedback with a localized confirmation message.
  • Bug Fixes

    • Optimized login form validation flow with improved error handling and messaging.
    • Enhanced notification styling and context-aware display for better user experience.

Copilot AI review requested due to automatic review settings March 23, 2026 13:01
@netlify
Copy link
Copy Markdown

netlify Bot commented Mar 23, 2026

Deploy Preview for cv-mobile-app-web ready!

Name Link
🔨 Latest commit 765f97f
🔍 Latest deploy log https://app.netlify.com/projects/cv-mobile-app-web/deploys/69c139b08909250008916999
😎 Deploy Preview https://deploy-preview-547--cv-mobile-app-web.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 23, 2026

Walkthrough

This change refactors the logout and snackbar handling across multiple files. The onLogoutPressed() method in the viewmodel now returns Future<bool> instead of Future<void> to indicate logout confirmation status, and no longer triggers snackbar display. The UI layer (cv_drawer.dart and login_view.dart) now handles snackbar notifications conditionally based on the returned boolean. Additionally, snackbar_utils.dart introduces a new showDarkWithContext() method using ScaffoldMessenger for context-aware snackbar display and consolidates snackbar creation logic into a private helper method. The dark snackbar background opacity is also adjusted from 0.8 to 0.85.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix: FlutterError on Snackbar Overlay' directly addresses the main issue being fixed—a FlutterError related to snackbar overlay timing. It clearly identifies the problem area and the type of fix being applied.
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 unit tests (beta)
  • Create PR with unit tests

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

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses the “No Overlay widget found” FlutterError occurring during authentication flows by changing when/how snackbars are shown so they aren’t triggered while the overlay is not yet available (login) or after it has been torn down (logout).

Changes:

  • Refactors SnackBarUtils to use a shared internal _show() that defers snackbar display via scheduling.
  • Updates login flow to navigate first and then show the success snackbar shortly after.
  • Moves logout success snackbar display out of the viewmodel and into the drawer UI, using ScaffoldMessengerState captured before logout actions.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
lib/viewmodels/cv_landing_viewmodel.dart Changes logout handler to return a bool for confirmation and removes snackbar side-effect from the viewmodel.
lib/utils/snackbar_utils.dart Reworks snackbar implementation to a centralized helper and adds a ScaffoldMessenger-based variant.
lib/ui/views/authentication/login_view.dart Adjusts login success flow to navigate before showing snackbar to avoid overlay timing issues.
lib/ui/components/cv_drawer.dart Shows logout success snackbar via ScaffoldMessenger captured before logout-triggered navigation changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +83 to +86
Future<bool> onLogoutPressed() async {
Get.back();

var _dialogResponse = await _dialogService.showConfirmationDialog(
var response = await _dialogService.showConfirmationDialog(
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

SnackBarUtils is no longer used in this viewmodel (the logout success snackbar was removed), so the snackbar_utils.dart import at the top of the file should be removed to avoid an unused_import lint/analyzer warning.

Copilot uses AI. Check for mistakes.
Comment on lines +93 to +96
if (response?.confirmed ?? false) {
onLogout();
selectedIndex = 0;
SnackBarUtils.showDark(
AppLocalizations.of(Get.context!)!.cv_logout_success,
AppLocalizations.of(Get.context!)!.cv_logout_success_acknowledgement,
);
return true;
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

onLogout() is an async function but returns void, so it can't be awaited here. As a result, onLogoutPressed() returns true (and the UI can show a “logout success” snackbar) before Google sign-out/session clearing has actually finished. Consider changing onLogout() to return Future<void> and awaiting it here before returning true (and handle/signals errors if sign-out fails).

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +65
if (Get.context == null) return;

if (Get.isSnackbarOpen) {
Get.closeCurrentSnackbar();
}

Future.microtask(() {
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

The Get.context == null guard happens before scheduling the microtask, but Get.rawSnackbar() runs later and can still throw if the overlay/context becomes unavailable in the meantime (the race this PR is addressing). To make this robust, re-check the required context/overlay inside the scheduled callback and/or use a post-frame callback (WidgetsBinding.instance.addPostFrameCallback) instead of Future.microtask so it runs after the next build when the overlay is mounted.

Suggested change
if (Get.context == null) return;
if (Get.isSnackbarOpen) {
Get.closeCurrentSnackbar();
}
Future.microtask(() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (Get.context == null) {
return;
}
if (Get.isSnackbarOpen) {
Get.closeCurrentSnackbar();
}

Copilot uses AI. Check for mistakes.
Comment on lines +224 to +251
final messenger = ScaffoldMessenger.of(context);
final logoutSuccess = AppLocalizations.of(context)!.cv_logout_success;
final logoutAck = AppLocalizations.of(context)!.cv_logout_success_acknowledgement;
final confirmed = await _model.onLogoutPressed();
if (confirmed) {
messenger.showSnackBar(
SnackBar(
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
logoutSuccess,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
Text(logoutAck, style: const TextStyle(color: Colors.white)),
],
),
backgroundColor: Colors.black.withValues(alpha: 0.85),
behavior: SnackBarBehavior.floating,
margin: const EdgeInsets.all(12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
duration: const Duration(seconds: 2),
),
);
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

This logout snackbar duplicates styling/markup that now also exists in SnackBarUtils (and is likely to be needed elsewhere). Consider centralizing this (e.g., add a SnackBarUtils.showDarkWithMessenger(ScaffoldMessengerState, ...) or similar) so the app has one consistent snackbar implementation and future style tweaks don't require editing multiple widgets.

Suggested change
final messenger = ScaffoldMessenger.of(context);
final logoutSuccess = AppLocalizations.of(context)!.cv_logout_success;
final logoutAck = AppLocalizations.of(context)!.cv_logout_success_acknowledgement;
final confirmed = await _model.onLogoutPressed();
if (confirmed) {
messenger.showSnackBar(
SnackBar(
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
logoutSuccess,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
Text(logoutAck, style: const TextStyle(color: Colors.white)),
],
),
backgroundColor: Colors.black.withValues(alpha: 0.85),
behavior: SnackBarBehavior.floating,
margin: const EdgeInsets.all(12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
duration: const Duration(seconds: 2),
),
);
final logoutSuccess = AppLocalizations.of(context)!.cv_logout_success;
final logoutAck = AppLocalizations.of(context)!.cv_logout_success_acknowledgement;
final confirmed = await _model.onLogoutPressed();
if (confirmed) {
SnackBarUtils.showDark(context, logoutSuccess, logoutAck);

Copilot uses AI. Check for mistakes.
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: 2

🧹 Nitpick comments (1)
lib/utils/snackbar_utils.dart (1)

23-51: Take a captured ScaffoldMessengerState here instead of BuildContext.

The safe value across drawer dismissal is the messenger, not the drawer context. That’s why lib/ui/components/cv_drawer.dart still has to inline the same SnackBar body instead of reusing this helper.

Suggested API shape
-  static void showDarkWithContext(
-    BuildContext context,
+  static void showDarkWithMessenger(
+    ScaffoldMessengerState messenger,
     String title,
     String message,
   ) {
-    ScaffoldMessenger.of(context).showSnackBar(
+    messenger.showSnackBar(
       SnackBar(

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2a5d5ae4-377c-4273-9013-b0324ac99cd6

📥 Commits

Reviewing files that changed from the base of the PR and between f621531 and 765f97f.

📒 Files selected for processing (4)
  • lib/ui/components/cv_drawer.dart
  • lib/ui/views/authentication/login_view.dart
  • lib/utils/snackbar_utils.dart
  • lib/viewmodels/cv_landing_viewmodel.dart

Comment on lines +139 to +145
await _model.login(_email, _password);

// Capture strings BEFORE any async navigation (important)
final successTitle = AppLocalizations.of(context)!.login_success_title;
final successMessage = AppLocalizations.of(context)!.login_success_message;

final errorTitle = AppLocalizations.of(context)!.login_error;
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

🏁 Script executed:

# First, let's locate and examine the login_view.dart file
fd "login_view.dart" --type f

Repository: CircuitVerse/mobile-app

Length of output: 110


🏁 Script executed:

# Get the content of the login view to understand the full context
cat -n lib/ui/views/authentication/login_view.dart | head -200

Repository: CircuitVerse/mobile-app

Length of output: 7786


🏁 Script executed:

# Search for the mounted pattern usage in the codebase to see if this is a common practice
rg "if \(!mounted\)" --type dart -B 2 -A 2 | head -50

Repository: CircuitVerse/mobile-app

Length of output: 49


🏁 Script executed:

# Check what class LoginView extends
ast-grep --pattern 'class LoginView $$$'

Repository: CircuitVerse/mobile-app

Length of output: 49


Add a mounted guard after the login await.

After await _model.login(...) completes, the widget could be disposed if the user navigates away during the request. This makes the subsequent AppLocalizations.of(context) calls unsafe—the context becomes stale and the inherited-widget lookup can fail or crash.

Suggested fix
     await _model.login(_email, _password);
+    if (!mounted) return;
 
     // Capture strings BEFORE any async navigation (important)

Comment on lines +83 to +96
Future<bool> onLogoutPressed() async {
Get.back();

var _dialogResponse = await _dialogService.showConfirmationDialog(
var response = await _dialogService.showConfirmationDialog(
title: AppLocalizations.of(Get.context!)!.cv_logout,
description: AppLocalizations.of(Get.context!)!.cv_logout_confirmation,
confirmationTitle:
AppLocalizations.of(Get.context!)!.cv_logout_confirmation_button,
);

if (_dialogResponse?.confirmed ?? false) {
if (response?.confirmed ?? false) {
onLogout();
selectedIndex = 0;
SnackBarUtils.showDark(
AppLocalizations.of(Get.context!)!.cv_logout_success,
AppLocalizations.of(Get.context!)!.cv_logout_success_acknowledgement,
);
return true;
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

🏁 Script executed:

# First, let me locate and examine the cv_landing_viewmodel.dart file
cd /tmp && git ls-files | grep -i "cv_landing_viewmodel"

Repository: CircuitVerse/mobile-app

Length of output: 135


🏁 Script executed:

# Examine the onLogoutPressed and onLogout methods
cat -n lib/viewmodels/cv_landing_viewmodel.dart | sed -n '40,100p'

Repository: CircuitVerse/mobile-app

Length of output: 1935


🏁 Script executed:

# Check how onLogoutPressed is used in cv_drawer.dart
fd "cv_drawer" --type f

Repository: CircuitVerse/mobile-app

Length of output: 137


🏁 Script executed:

# Examine how onLogoutPressed is used in cv_drawer.dart
cat -n lib/ui/components/cv_drawer.dart | grep -A 5 -B 5 "onLogoutPressed"

Repository: CircuitVerse/mobile-app

Length of output: 1456


🏁 Script executed:

# Let me also check the full context around the logout snackbar to confirm the usage
cat -n lib/ui/components/cv_drawer.dart | sed -n '220,240p'

Repository: CircuitVerse/mobile-app

Length of output: 1399


Await logout completion before returning success.

onLogoutPressed() gates the logout-success snackbar, but onLogout() is async void, so it returns immediately before GoogleSignIn.signOut() completes. If signout fails, the exception is swallowed and the method still returns true, causing the caller to show a success message even though logout failed.

Change onLogout() to return Future<void> and await it before resetting state and returning:

Suggested fix
-  void onLogout() async {
+  Future<void> onLogout() async {
     _storage.isLoggedIn = false;
     _storage.currentUser = null;
     _storage.token = null;
@@
-      onLogout();
+      await onLogout();
       selectedIndex = 0;
       return true;

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.

2 participants