1+ import 'dart:async' ;
2+
3+ import 'package:flutter/foundation.dart' ;
14import 'package:flutter/material.dart' ;
5+ import 'package:flutter/scheduler.dart' ;
26import 'package:flutter_gen/gen_l10n/zulip_localizations.dart' ;
37
48import '../model/localizations.dart' ;
@@ -13,17 +17,65 @@ import 'store.dart';
1317class ZulipApp extends StatelessWidget {
1418 const ZulipApp ({super .key, this .navigatorObservers});
1519
20+ /// Whether the app's widget tree is ready.
21+ ///
22+ /// This begins as false. It transitions to true when the
23+ /// [GlobalStore] has been loaded and the [MaterialApp] has been mounted,
24+ /// and then remains true.
25+ static ValueListenable <bool > get ready => _ready;
26+ static ValueNotifier <bool > _ready = ValueNotifier (false );
27+
28+ /// The navigator for the whole app.
29+ ///
30+ /// This is always the [GlobalKey.currentState] of [navigatorKey] .
31+ /// If [navigatorKey] is already mounted, this future completes immediately.
32+ /// Otherwise, it waits for [ready] to become true and then completes.
33+ static Future <NavigatorState > get navigator {
34+ final state = navigatorKey.currentState;
35+ if (state != null ) return Future .value (state);
36+
37+ assert (! ready.value);
38+ final completer = Completer <NavigatorState >();
39+ ready.addListener (() {
40+ assert (ready.value);
41+ completer.complete (navigatorKey.currentState! );
42+ });
43+ return completer.future;
44+ }
45+
1646 /// A key for the navigator for the whole app.
1747 ///
1848 /// For code that exists entirely outside the widget tree and has no natural
1949 /// [BuildContext] of its own, this enables interacting with the app's
2050 /// navigation, by calling [GlobalKey.currentState] to get a [NavigatorState] .
51+ ///
52+ /// During the app's early startup, this key will not yet be mounted.
53+ /// It will always be mounted before [ready] becomes true,
54+ /// and naturally before any widgets are mounted which are part of the
55+ /// app's main UI managed by the navigator.
56+ ///
57+ /// See also [navigator] , to asynchronously wait for the navigator
58+ /// to be mounted.
2159 static final navigatorKey = GlobalKey <NavigatorState >();
2260
61+ /// Reset the state of [ZulipApp] statics, for testing.
62+ ///
63+ /// TODO refactor this better, perhaps unify with ZulipBinding
64+ @visibleForTesting
65+ static void debugReset () {
66+ _ready.dispose ();
67+ _ready = ValueNotifier (false );
68+ }
69+
2370 /// A list to pass through to [MaterialApp.navigatorObservers] .
2471 /// Useful in tests.
2572 final List <NavigatorObserver >? navigatorObservers;
2673
74+ void _declareReady () {
75+ assert (navigatorKey.currentContext != null );
76+ _ready.value = true ;
77+ }
78+
2779 @override
2880 Widget build (BuildContext context) {
2981 final theme = ThemeData (
@@ -64,6 +116,10 @@ class ZulipApp extends StatelessWidget {
64116 navigatorKey: navigatorKey,
65117 navigatorObservers: navigatorObservers ?? const [],
66118 builder: (BuildContext context, Widget ? child) {
119+ if (! ready.value) {
120+ SchedulerBinding .instance.addPostFrameCallback (
121+ (_) => _declareReady ());
122+ }
67123 GlobalLocalizations .zulipLocalizations = ZulipLocalizations .of (context);
68124 return child! ;
69125 },
0 commit comments