1
+ import 'dart:async' ;
2
+
3
+ import 'package:flutter/foundation.dart' ;
1
4
import 'package:flutter/material.dart' ;
5
+ import 'package:flutter/scheduler.dart' ;
2
6
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart' ;
3
7
4
8
import '../model/localizations.dart' ;
@@ -13,17 +17,65 @@ import 'store.dart';
13
17
class ZulipApp extends StatelessWidget {
14
18
const ZulipApp ({super .key, this .navigatorObservers});
15
19
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
+
16
46
/// A key for the navigator for the whole app.
17
47
///
18
48
/// For code that exists entirely outside the widget tree and has no natural
19
49
/// [BuildContext] of its own, this enables interacting with the app's
20
50
/// 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.
21
59
static final navigatorKey = GlobalKey <NavigatorState >();
22
60
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
+
23
70
/// A list to pass through to [MaterialApp.navigatorObservers] .
24
71
/// Useful in tests.
25
72
final List <NavigatorObserver >? navigatorObservers;
26
73
74
+ void _declareReady () {
75
+ assert (navigatorKey.currentContext != null );
76
+ _ready.value = true ;
77
+ }
78
+
27
79
@override
28
80
Widget build (BuildContext context) {
29
81
final theme = ThemeData (
@@ -64,6 +116,10 @@ class ZulipApp extends StatelessWidget {
64
116
navigatorKey: navigatorKey,
65
117
navigatorObservers: navigatorObservers ?? const [],
66
118
builder: (BuildContext context, Widget ? child) {
119
+ if (! ready.value) {
120
+ SchedulerBinding .instance.addPostFrameCallback (
121
+ (_) => _declareReady ());
122
+ }
67
123
GlobalLocalizations .zulipLocalizations = ZulipLocalizations .of (context);
68
124
return child! ;
69
125
},
0 commit comments