Skip to content

Commit d9f88f3

Browse files
gnpricechrisbobbe
authored andcommitted
store: Add details to error on no PerAccountStoreWidget
This draws from MediaQuery.of and debugCheckHasMediaQuery. Prompted by this question: https://chat.zulip.org/#narrow/channel/516-mobile-dev-help/topic/No.20PerAccountStoreWidget.20ancestor/near/2008484
1 parent f2458b1 commit d9f88f3

File tree

2 files changed

+37
-2
lines changed

2 files changed

+37
-2
lines changed

lib/widgets/store.dart

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class PerAccountStoreWidget extends StatefulWidget {
164164
/// * [InheritedNotifier], which provides the "dependency" mechanism.
165165
static PerAccountStore of(BuildContext context) {
166166
final widget = context.dependOnInheritedWidgetOfExactType<_PerAccountStoreInheritedWidget>();
167-
assert(widget != null, 'No PerAccountStoreWidget ancestor');
167+
assert(_debugCheckFound(context, widget));
168168
return widget!.store;
169169
}
170170

@@ -183,12 +183,29 @@ class PerAccountStoreWidget extends StatefulWidget {
183183
/// Like [of], the cost of this method is O(1) with a small constant factor.
184184
static int accountIdOf(BuildContext context) {
185185
final element = context.getElementForInheritedWidgetOfExactType<_PerAccountStoreInheritedWidget>();
186-
assert(element != null, 'No PerAccountStoreWidget ancestor');
186+
assert(_debugCheckFound(context, element));
187187
final widget = element!.findAncestorWidgetOfExactType<PerAccountStoreWidget>();
188188
assert(widget != null);
189189
return widget!.accountId;
190190
}
191191

192+
static bool _debugCheckFound(BuildContext context, Object? ancestor) {
193+
// Compare [debugCheckHasMediaQuery], and its caller [MediaQuery.of].
194+
assert(() {
195+
if (ancestor != null) return true;
196+
throw FlutterError.fromParts([
197+
ErrorSummary('No PerAccountStoreWidget ancestor found.'),
198+
ErrorDescription('${context.widget.runtimeType} widgets require a PerAccountStoreWidget ancestor.'),
199+
context.describeWidget('The specific widget that could not find a PerAccountStoreWidget ancestor was'),
200+
context.describeOwnershipChain('The ownership chain for the affected widget is'),
201+
ErrorHint('For a new page in the app, consider MaterialAccountWidgetRoute '
202+
'or AccountPageRouteBuilder.'),
203+
ErrorHint('In tests, consider TestZulipApp with its accountId field.'),
204+
]);
205+
}());
206+
return true;
207+
}
208+
192209
/// Whether there is a relevant account specified for this widget.
193210
static bool debugExistsOf(BuildContext context) {
194211
return context.getElementForInheritedWidgetOfExactType<_PerAccountStoreInheritedWidget>() != null;

test/widgets/store_test.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,24 @@ void main() {
9595
tester.widget(find.text('found store, account: ${eg.selfAccount.id}'));
9696
});
9797

98+
testWidgets('PerAccountStoreWidget.of detailed error', (tester) async {
99+
addTearDown(testBinding.reset);
100+
await tester.pumpWidget(
101+
Directionality(
102+
textDirection: TextDirection.ltr,
103+
child: GlobalStoreWidget(
104+
// no PerAccountStoreWidget
105+
child: Builder(
106+
builder: (context) {
107+
final store = PerAccountStoreWidget.of(context);
108+
return Text('found store, account: ${store.accountId}');
109+
}))));
110+
await tester.pump();
111+
check(tester.takeException())
112+
.has((x) => x.toString(), 'toString') // TODO(checks): what's a good convention for this?
113+
.contains('consider MaterialAccountWidgetRoute');
114+
});
115+
98116
testWidgets('PerAccountStoreWidget immediate data after first loaded', (tester) async {
99117
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
100118
addTearDown(testBinding.reset);

0 commit comments

Comments
 (0)