Files
buzz/mobile/lib/main.dart
Tom Brow 5996e5dd8b fix(mobile): isolate push setup failures from app rendering (#8610)
Push subscription setup ran synchronously inside the root widget build,
so an invalid subscription could replace the entire app with Flutter's
error surface. Push initialization and the cold-start native
notification read could also prevent the app from rendering.

Keep push work independent of the app subtree, observe asynchronous
persistence failures, and retain the original errors and stack traces.
Setup retries use bounded backoff; Settings shows a failure and offers
manual retry without resetting navigation. Retry preserves a healthy
revocation worker, and community-removal callbacks follow a recovered
worker so lease cleanup continues after setup recovery.

Related: [#8608](https://github.com/block/buzz/pull/8608) fixes the
specific unrelated-mute input that exposed this startup coupling.

### Validation

Added Flutter journeys cover invalid subscriptions, asynchronous storage
failure, dependency initialization failure, retry exhaustion and manual
recovery, navigation preservation, accessible retry actions, and
pending/failed native startup reads with notification-target recovery.
Both original rendering regressions failed before the fix.
Community-removal coverage also verifies cleanup after a worker
initialization failure and after retrying healthy setup; that regression
failed before the worker fix.

The full mobile suite passed (3,782 tests; 19 existing skips), as did
the four unconfigured-build tests and static analysis. The corrected
head also passed the full pre-push mobile checks and tests, plus 42
focused push and community tests. Rendered widget evidence uses mocked
native platform channels; no device APNs delivery test was performed.

Rendered Flutter widget evidence with synthetic data and mocked native
platform views.

### Setup failure
The app remains usable and Settings offers a retry after push
initialization fails.


![notification-setup-failed](https://raw.githubusercontent.com/block/buzz/af12b52923a75c6ad9fda774b9f4b3af37961422/pr-8610--notification-setup-failed.png)

### Recovery
Retry clears the error without replacing the navigator or leaving
Settings.


![notification-setup-recovered](https://raw.githubusercontent.com/block/buzz/af12b52923a75c6ad9fda774b9f4b3af37961422/pr-8610--notification-setup-recovered.png)

<!-- buzz-review-completed -->

---------

Signed-off-by: Tom Brow <tomb@block.xyz>
2026-10-26 19:32:38 -07:00

44 lines
1.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'app.dart';
import 'features/age_gate/age_signal_push_bootstrap.dart';
import 'features/invites/invite_join_provider.dart';
import 'shared/push/push_bridge.dart';
import 'shared/relay/relay_closed_policy.dart';
import 'shared/theme/theme_provider.dart';
void main() => runBuzzApp(const App());
Future<void> runBuzzApp(Widget app) async {
WidgetsFlutterBinding.ensureInitialized();
installBuzzPushMethodHandler();
// Pre-load preferences so the first frame uses the saved theme/accent.
final prefs = await SharedPreferences.getInstance();
runApp(buildRootProviderScope(prefs: prefs, child: app));
}
/// The app's root scope, shared with tests so its production wiring is
/// asserted rather than re-declared.
ProviderScope buildRootProviderScope({
required SharedPreferences prefs,
required Widget child,
}) => ProviderScope(
// A relay statement deadline cannot resolve on retry — the same expensive
// query would re-run and hit the same limit. Return null (no retry) for
// deadline errors and fall through to the default exponential backoff for
// everything else.
retry: relayProviderRetry,
overrides: [
savedPrefsProvider.overrideWithValue(prefs),
inviteJoinRecoveryProvider.overrideWith(
(ref) =>
(scope) => buildMobileInviteJoinRecovery(ref, scope),
),
],
child: AgeSignalPushBootstrap(child: child),
);