diff --git a/lib/main.dart b/lib/main.dart index f76d4cc3..76686a5e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,31 +1,24 @@ -import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:starter_architecture_flutter_firebase/firebase_options.dart'; import 'package:starter_architecture_flutter_firebase/src/app.dart'; +import 'package:starter_architecture_flutter_firebase/src/app_startup.dart'; import 'package:starter_architecture_flutter_firebase/src/localization/string_hardcoded.dart'; -import 'package:starter_architecture_flutter_firebase/src/features/onboarding/data/onboarding_repository.dart'; // ignore:depend_on_referenced_packages import 'package:flutter_web_plugins/url_strategy.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - await Firebase.initializeApp( - options: DefaultFirebaseOptions.currentPlatform, - ); // turn off the # in the URLs on the web usePathUrlStrategy(); // * Register error handlers. For more info, see: // * https://docs.flutter.dev/testing/errors registerErrorHandlers(); // * Entry point of the app - - final container = ProviderContainer(); - await container.read(onboardingRepositoryProvider.future); - runApp(UncontrolledProviderScope( - container: container, - child: const MyApp(), + runApp(ProviderScope( + child: AppStartupWidget( + onLoaded: (context) => const MyApp(), + ), )); } diff --git a/lib/src/app_startup.dart b/lib/src/app_startup.dart new file mode 100644 index 00000000..a3707ad9 --- /dev/null +++ b/lib/src/app_startup.dart @@ -0,0 +1,82 @@ +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:starter_architecture_flutter_firebase/firebase_options.dart'; +import 'package:starter_architecture_flutter_firebase/src/constants/app_sizes.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/onboarding/data/onboarding_repository.dart'; + +part 'app_startup.g.dart'; + +@Riverpod(keepAlive: true) +Future appStartup(AppStartupRef ref) async { + // await for all initialization code to be complete before returning + await Future.wait([ + // Firebase init + Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform), + // list of providers to be warmed up + ref.watch(onboardingRepositoryProvider.future) + ]); +} + +/// Widget class to manage asynchronous app initialization +class AppStartupWidget extends ConsumerWidget { + const AppStartupWidget({super.key, required this.onLoaded}); + final WidgetBuilder onLoaded; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final appStartupState = ref.watch(appStartupProvider); + return appStartupState.when( + data: (_) => onLoaded(context), + loading: () => const AppStartupLoadingWidget(), + error: (e, st) => AppStartupErrorWidget( + message: e.toString(), + onRetry: () => ref.refresh(appStartupProvider), + ), + ); + } +} + +class AppStartupLoadingWidget extends StatelessWidget { + const AppStartupLoadingWidget({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ), + ); + } +} + +class AppStartupErrorWidget extends StatelessWidget { + const AppStartupErrorWidget( + {super.key, required this.message, required this.onRetry}); + final String message; + final VoidCallback onRetry; + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text(message, style: Theme.of(context).textTheme.headlineSmall), + gapH16, + ElevatedButton( + onPressed: onRetry, + child: const Text('Retry'), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/app_startup.g.dart b/lib/src/app_startup.g.dart new file mode 100644 index 00000000..2c566956 --- /dev/null +++ b/lib/src/app_startup.g.dart @@ -0,0 +1,24 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_startup.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$appStartupHash() => r'26df5b72ccd13f52173f1630996eccacb09eb593'; + +/// See also [appStartup]. +@ProviderFor(appStartup) +final appStartupProvider = FutureProvider.internal( + appStartup, + name: r'appStartupProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$appStartupHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef AppStartupRef = FutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member