From 9aa047109228d086d9d2d3bc9c72737263afa47d Mon Sep 17 00:00:00 2001 From: Greg Price Date: Fri, 8 Mar 2024 21:21:49 -0800 Subject: [PATCH] app: Add ZulipApp.scaffoldMessenger Also generalize ValueNotifierChecks to ValueListenableChecks. We can't write checks-extensions for ZulipApp.scaffoldMessenger or ZulipApp.ready, though, because Dart doesn't have extensions for static members. Apparently they're currently working on them: https://github.com/dart-lang/language/issues/723 Co-authored-by: Zixuan James Li --- lib/widgets/app.dart | 16 ++++++++++++++++ test/flutter_checks.dart | 3 ++- test/widgets/app_test.dart | 13 +++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/widgets/app.dart b/lib/widgets/app.dart index 38abaad4a7..418bd36e5e 100644 --- a/lib/widgets/app.dart +++ b/lib/widgets/app.dart @@ -63,6 +63,22 @@ class ZulipApp extends StatefulWidget { /// to be mounted. static final navigatorKey = GlobalKey(); + /// The [ScaffoldMessengerState] for the app. + /// + /// This is null during the app's early startup, while [ready] is still false. + /// + /// For code that exists entirely outside the widget tree and has no natural + /// [BuildContext] of its own, this enables controlling snack bars. + /// Where a relevant [BuildContext] does exist, prefer using that instead, + /// with [ScaffoldMessenger.of]. + static ScaffoldMessengerState? get scaffoldMessenger { + final context = navigatorKey.currentContext; + if (context == null) return null; + // Not maybeOf; we use MaterialApp, which provides ScaffoldMessenger, + // so it's a bug if navigatorKey is mounted somewhere lacking that. + return ScaffoldMessenger.of(context); + } + /// Reset the state of [ZulipApp] statics, for testing. /// /// TODO refactor this better, perhaps unify with ZulipBinding diff --git a/test/flutter_checks.dart b/test/flutter_checks.dart index 641a2d8de1..3a2b2f2239 100644 --- a/test/flutter_checks.dart +++ b/test/flutter_checks.dart @@ -2,6 +2,7 @@ library; import 'package:checks/checks.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -51,7 +52,7 @@ extension RouteSettingsChecks on Subject { Subject get arguments => has((s) => s.arguments, 'arguments'); } -extension ValueNotifierChecks on Subject> { +extension ValueListenableChecks on Subject> { Subject get value => has((c) => c.value, 'value'); } diff --git a/test/widgets/app_test.dart b/test/widgets/app_test.dart index 62d6e47abf..a888fd9658 100644 --- a/test/widgets/app_test.dart +++ b/test/widgets/app_test.dart @@ -157,4 +157,17 @@ void main() { ..bottom.isLessThan(2 / 3 * screenHeight); }); }); + + group('scaffoldMessenger', () { + testWidgets('scaffoldMessenger becomes non-null after startup', (tester) async { + addTearDown(testBinding.reset); + await tester.pumpWidget(const ZulipApp()); + + check(ZulipApp.scaffoldMessenger).isNull(); + check(ZulipApp.ready).value.isFalse(); + await tester.pump(); + check(ZulipApp.scaffoldMessenger).isNotNull(); + check(ZulipApp.ready).value.isTrue(); + }); + }); }