Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/nonstop_cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.0.8+2

- **FIX**(nonstop_cli): boot apps before flutterfire configure.

## 0.0.8+1

- Update a dependency to the latest release.
Expand Down
5 changes: 3 additions & 2 deletions packages/nonstop_cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,9 @@ youtube/

The generated workspace is bootstrapped, formatted and passes its own
`melos lint` and `melos test` before you touch it. If you picked any Firebase
module, run `flutterfire configure` in the app directory - the generated
`firebase_options.dart` throws until you do.
module, run `flutterfire configure` in the app directory when you want those
features live. Until then the app still runs; Firebase-backed modules are
skipped at startup.

</details>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ melos bootstrap
{{#firebase}}
### Firebase

This project uses Firebase, so configure it before the first run - the
generated `lib/firebase_options.dart` deliberately throws until you do:
This project uses Firebase, so run this before those features will work.
The rest of the app still boots without it:

```sh
dart pub global activate flutterfire_cli
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class _AppState extends State<App> {
// sign-in once you would rather ask a user who has committed to the app.
_lifecycleListener = AppLifecycleListener(onResume: _clearBadge);
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!di.has<NotificationClient>()) return;
final logger = di.get<Logger>();
try {
await di.get<NotificationClient>().init();
Expand All @@ -34,6 +35,7 @@ class _AppState extends State<App> {
{{/notifications}} }
{{#notifications}}
void _clearBadge() {
if (!di.has<NotificationClient>()) return;
final logger = di.get<Logger>();
try {
di.get<NotificationClient>().clearBadge();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import 'package:core/core.dart' as core;
///
/// Order matters: the logger is registered first so everything after it can
/// report failures, and Firebase must be live before any Firebase-backed
/// module initialises.
/// module initialises. If `flutterfire configure` has not been run yet, those
/// modules are skipped so the rest of the app still boots.
Future<void> init() async {
WidgetsFlutterBinding.ensureInitialized();
final stopwatch = Stopwatch()..start();
Expand All @@ -28,32 +29,47 @@ Future<void> init() async {

Bloc.observer = core.CoreBlocObserver();
{{#firebase}}
final options = DefaultFirebaseOptions.currentPlatform;
await Firebase.initializeApp(options: options);

if (core.Environment.useEmulators) {
await emulators.init();
var firebaseReady = false;
try {
final options = DefaultFirebaseOptions.currentPlatform;
await Firebase.initializeApp(options: options);
firebaseReady = true;
if (core.Environment.useEmulators) {
await emulators.init();
}
} on UnsupportedError catch (error) {
logger.w('$error');
}
logger.i(
firebaseReady
? 'Firebase ready'
: 'Firebase not configured yet; skipping Firebase modules',
);
{{/firebase}}
{{#crashlytics}} await crashlytics.init();
{{/crashlytics}}{{#analytics}} await analytics.init();
{{/analytics}}{{#feature_flags}} await feature_flags.init();
{{#crashlytics}} if (firebaseReady) await crashlytics.init();
{{/crashlytics}}{{#analytics}} if (firebaseReady) await analytics.init();
{{/analytics}}{{#feature_flags}} if (firebaseReady) await feature_flags.init();
{{/feature_flags}}{{#auth}}
final clientId = options.iosClientId ?? options.androidClientId ?? '';
await auth.init(auth.DefaultAuthConfig(clientId: clientId));
if (firebaseReady) {
final options = DefaultFirebaseOptions.currentPlatform;
final clientId = options.iosClientId ?? options.androidClientId ?? '';
await auth.init(auth.DefaultAuthConfig(clientId: clientId));
}
{{/auth}}{{#network}}
await network.init(
config: network.DefaultNetworkConfig(baseUrl: core.Environment.baseUrl),
);
{{/network}}{{#notifications}}
await notifications.init();
if (firebaseReady) await notifications.init();
{{/notifications}}
// TODO: initialise your own feature modules here.

stopwatch.stop();
logger.i('Bootstrap completed in ${stopwatch.elapsedMilliseconds} ms');
{{#analytics}}
await analytics.AnalyticsHelper.logAppOpen(
parameters: {'bootstrap_duration': stopwatch.elapsedMilliseconds},
);
if (firebaseReady) {
await analytics.AnalyticsHelper.logAppOpen(
parameters: {'bootstrap_duration': stopwatch.elapsedMilliseconds},
);
}
{{/analytics}}}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
{{/dashboard}}import 'package:design_system/design_system.dart';
{{#developer}}import 'package:developer/developer.dart' as developer;
{{/developer}}import 'package:di/di.dart';
import 'package:flutter/foundation.dart';
{{#analytics}}import 'package:firebase_core/firebase_core.dart';
{{/analytics}}import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:localization/localization.dart';
Expand Down Expand Up @@ -36,7 +37,7 @@ class AppRouter {
observers: [
NavigationHistoryObserver(),
core.CoreRouteObserver(),
{{#analytics}} AnalyticsRouteObserver(),
{{#analytics}} if (Firebase.apps.isNotEmpty) AnalyticsRouteObserver(),
{{/analytics}} ],
errorBuilder: (context, state) => ErrorScreen(
title: strings.errors.page_not_found,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class _SplashScreenState extends State<SplashScreen> {
Future<void> _redirect() async {
if (!mounted) return;
{{#auth}}
if (!di.get<auth.AuthService>().isSignedIn) {
if (di.has<auth.AuthService>() &&
!di.get<auth.AuthService>().isSignedIn) {
context.go(auth.AuthRoutes.signIn);
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
{{#firebase}}// This file is a placeholder. Generate the real one with:
// This file is a placeholder. Generate the real one with:
//
// cd apps/{{name.snakeCase()}} && flutterfire configure
//
// FlutterFire overwrites this file with your project's options and also drops
// google-services.json / GoogleService-Info.plist into the native folders.
// Until then, bootstrap catches the throw and skips Firebase-backed modules
// so the rest of the app still runs.
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;

class DefaultFirebaseOptions {
Expand All @@ -14,4 +16,4 @@ class DefaultFirebaseOptions {
'Run `flutterfire configure` inside apps/{{name.snakeCase()}} '
'to replace lib/firebase_options.dart.',
);
}{{/firebase}}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ final class GoAuthRoute extends GoRoute {
super.pageBuilder,
}) : super(
redirect: (context, state) async {
if (!di.has<AuthService>()) return null;

final authService = di.get<AuthService>();

if (!authService.isSignedIn) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class ProfileTabScreen extends StatelessWidget {
);

if (confirmed != true || !context.mounted) return;
if (!di.has<auth.AuthService>()) return;

await di.get<auth.AuthService>().signOut();
if (context.mounted) context.go(auth.AuthRoutes.signIn);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,14 @@ void resolveModuleVars(HookContext context) {
final usesFirebase =
_firebaseBackedModules.any((module) => _flag(context, module));
context.vars['firebase'] = usesFirebase;
// Empty path segment → mason skips the file, so a project with no Firebase
// modules never ships a placeholder `firebase_options.dart`.
context.vars['firebase_options_file'] =
usesFirebase ? 'firebase_options.dart' : '';

// The boot test runs the real bootstrap, which a Firebase project cannot do
// until `flutterfire configure` has been run. An empty file name makes mason
// skip the file, so those projects simply ship without it.
// The boot test runs the real bootstrap. Firebase projects can boot before
// `flutterfire configure` now (those modules are skipped), but after
// configure the splash may land on sign-in instead of the dashboard, so
// they still don't ship this file.
context.vars['smoke_test_file'] = usesFirebase ? '' : 'smoke_test.dart';
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ final class NextStepsCommand extends CliCommand {
..info(' dart pub global activate flutterfire_cli')
..info(' cd $appName/apps/$appName && flutterfire configure')
..info(
' (until then lib/firebase_options.dart throws on purpose)',
' (the app still runs; Firebase features stay off until then)',
);
}

Expand Down
Loading
Loading