diff --git a/android/app/src/main/res/values-night-v31/styles.xml b/android/app/src/main/res/values-night-v31/styles.xml
index 52d1073e..a801c54b 100644
--- a/android/app/src/main/res/values-night-v31/styles.xml
+++ b/android/app/src/main/res/values-night-v31/styles.xml
@@ -15,6 +15,6 @@
This Theme is only used starting with V2 of Flutter's Android embedding. -->
diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml
index dbc9ea9f..a3496baf 100644
--- a/android/app/src/main/res/values-night/styles.xml
+++ b/android/app/src/main/res/values-night/styles.xml
@@ -17,6 +17,6 @@
This Theme is only used starting with V2 of Flutter's Android embedding. -->
diff --git a/android/app/src/main/res/values-v31/styles.xml b/android/app/src/main/res/values-v31/styles.xml
index a6497f85..c71b5755 100644
--- a/android/app/src/main/res/values-v31/styles.xml
+++ b/android/app/src/main/res/values-v31/styles.xml
@@ -15,6 +15,6 @@
This Theme is only used starting with V2 of Flutter's Android embedding. -->
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
index 0d1fa8fc..ec261ecd 100644
--- a/android/app/src/main/res/values/styles.xml
+++ b/android/app/src/main/res/values/styles.xml
@@ -17,6 +17,6 @@
This Theme is only used starting with V2 of Flutter's Android embedding. -->
diff --git a/lib/main.dart b/lib/main.dart
index ad8f8130..51477a6a 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -13,37 +13,52 @@ import 'package:spark/src/core/utils/logging/riverpod_logger.dart';
import 'package:spark/src/sprk_app.dart';
void main() async {
- WidgetsFlutterBinding.ensureInitialized();
+ final binding = WidgetsFlutterBinding.ensureInitialized();
+ binding.deferFirstFrame();
+ var didAllowFirstFrame = false;
- await dotenv.load();
- SystemChrome.setPreferredOrientations([
- DeviceOrientation.portraitUp,
- DeviceOrientation.portraitDown,
- ]);
+ void allowFirstFrame() {
+ if (didAllowFirstFrame) {
+ return;
+ }
+ didAllowFirstFrame = true;
+ binding.allowFirstFrame();
+ }
- await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
- SystemChrome.setSystemUIOverlayStyle(AppTheme.darkSystemUiStyle);
+ try {
+ await dotenv.load();
+ await SystemChrome.setPreferredOrientations([
+ DeviceOrientation.portraitUp,
+ DeviceOrientation.portraitDown,
+ ]);
- fvp.registerWith();
+ await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
+ SystemChrome.setSystemUIOverlayStyle(AppTheme.darkSystemUiStyle);
- await initServiceLocator();
+ fvp.registerWith();
- // Setup logging for production/debug
- _setupLogging();
+ await initServiceLocator();
- // Initialize auth repository
- await _initializeAuth();
+ // Setup logging for production/debug
+ _setupLogging();
- // Create a ProviderContainer with the Riverpod logger
- final container = riverpod.ProviderContainer(
- observers: [SparkRiverpodLogger()],
- );
- runApp(
- riverpod.UncontrolledProviderScope(
- container: container,
- child: const SprkApp(),
- ),
- );
+ // Initialize auth repository
+ await _initializeAuth();
+
+ // Create a ProviderContainer with the Riverpod logger
+ final container = riverpod.ProviderContainer(
+ observers: [SparkRiverpodLogger()],
+ );
+ runApp(
+ riverpod.UncontrolledProviderScope(
+ container: container,
+ child: SprkApp(onFirstRouteReady: allowFirstFrame),
+ ),
+ );
+ } catch (_) {
+ allowFirstFrame();
+ rethrow;
+ }
}
/// Setup logging framework based on environment
diff --git a/lib/src/sprk_app.dart b/lib/src/sprk_app.dart
index 2108cd13..0f3cc2d4 100644
--- a/lib/src/sprk_app.dart
+++ b/lib/src/sprk_app.dart
@@ -13,7 +13,9 @@ import 'package:spark/src/features/feed/providers/feed_provider.dart';
import 'package:spark/src/features/settings/providers/settings_provider.dart';
class SprkApp extends ConsumerStatefulWidget {
- const SprkApp({super.key});
+ const SprkApp({required this.onFirstRouteReady, super.key});
+
+ final VoidCallback onFirstRouteReady;
@override
ConsumerState createState() => _SprkAppState();
@@ -22,10 +24,12 @@ class SprkApp extends ConsumerStatefulWidget {
class _SprkAppState extends ConsumerState {
final _appRouter = AppRouter();
final SparkLogger _logger = GetIt.instance().getLogger('SprkApp');
+ bool _didReleaseFirstFrame = false;
@override
void initState() {
super.initState();
+ _appRouter.addListener(_releaseFirstFrameWhenRouteIsReady);
// Register AppRouter globally for push notification navigation
if (!GetIt.instance.isRegistered()) {
GetIt.instance.registerSingleton(_appRouter);
@@ -39,6 +43,24 @@ class _SprkAppState extends ConsumerState {
});
}
+ @override
+ void dispose() {
+ _appRouter.removeListener(_releaseFirstFrameWhenRouteIsReady);
+ super.dispose();
+ }
+
+ void _releaseFirstFrameWhenRouteIsReady() {
+ if (_didReleaseFirstFrame || !_appRouter.hasEntries) {
+ return;
+ }
+
+ _didReleaseFirstFrame = true;
+ _appRouter.removeListener(_releaseFirstFrameWhenRouteIsReady);
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ widget.onFirstRouteReady();
+ });
+ }
+
Future _initializeApp() async {
try {
_logger.d('Syncing user preferences from server...');
@@ -89,6 +111,7 @@ class _SprkAppState extends ConsumerState {
return supportedLocales.first;
},
routerConfig: _appRouter.config(
+ placeholder: (_) => const ColoredBox(color: Colors.black),
navigatorObservers: () => [
PosthogObserver(nameExtractor: _postHogScreenName),
],