Let's say you have a Flutter App that you initialize using sth like:
void main() {
/// Required to assure that locking the device orientation and then running
/// the app works as intended
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((fn) {
runApp(
/// Riverpod being used
const ProviderScope(
child: MyApp(),
),
);
});
}
You want the sequence of bootup processes to be:
- Load and launch a lottie animation video of your brand, using this package and sth like:
Lottie.asset(
'assets/splash.json',
controller: _controller,
onLoaded: (composition) {
/// As soon as the lottie video has finished loading, start
/// playing it
_controller
..duration = composition.duration
..forward();
/// Do whatever you have to initialize here in the background, while the
/// video is playing
},
)
- As shown in the code above, you want to execute some kind of startup processes in the background, while the lottie video is playing (e.g. retrieve some values from the shared preferences, execute fast HTTP Requests, etc.). For example, the user must be able to change the mobile app's language within the mobile app itself. This comes with the fact that, if the user previously set a preferred language within the Shared Preferences storage (to survive app reboot), its retrieval is asynchronous.
- As soon as the video concludes, and your code assures that all of your asynchronous operations have been finished with the according awaits, launch your app's actual
MaterialApp/CupertinoAppwidget.
To be able to do what I want, I first thought of calling runApp() twice, but the problem with this is that my startup code initializes riverpod stuff, and the ProviderScope is limited to a single runApp() scope, according to the docs.
So to reach what I want, I thought of :
- Initializing the app in
main()with sth likerunApp(Center(child: MyLottieAnimation())), withMyLottieAnimation()being aCenter()widget holding theLottie.asset()widget shown above as child (so noMaterialApporCupertinoAppwidget at the very start). - When the animation and the tasks executed in the background conclude, call
Navigator.of(context).pushReplacement(), yielding the rootMaterialApp()widget. - That root
MaterialApp()widget is actually aConsumerStatefulWidget(), listening to changes to the locale in-app via sth likeref.watch(inAppLocaleProvider);within itsbuildmethod (and thus rebuilding whenever that value changes, by adapting thelocaleproperty of the accordingMaterialApp).
For this to work, I wanted to ask:
A) Are there risks coming from calling runApp() not returning any of MaterialApp or CupertinoApp at the beginning ? The docs say that MaterialApp is required for specific widgets of the Material Design to Work properly. But is this risk flutter-specific, or device-specific? E.g. when I run my Flutter app in the above-mentioned way on a random testing device and flutter reports no error, is there any chance that the app bootup may fail for some devices, due to the initial root widget not being MaterialApp or CupertinoApp ?
B) If it turns out that the outermost widget must be a MaterialApp widget, is it possible to initiate the app with a MaterialApp widget wrapping the lottie animation, which at its end transitions to another MaterialApp widget using pushReplacement, as mentioned above? I know you should only have a single MaterialApp widget per app, but what if one you use replaces the other in that way ?
C) Could it become a problem to re-build the outermost MaterialApp() widget upon a locale change ? Should you rather maybe provide a static locale to the MaterialApp's locale property, and provide a Localizations.override() widget as the home widget of it, and listen to locale changes only there ?