I’m constructing an app for ios/android utilizing flutter. When the app begins, the move is:
fundamental.dart
native splash displaying dev brand (throughout which app begin up occurs)
authgate
app widget (extracted this from fundamental to cut back how a lot work fundamental was doing)
flutter splash displaying app brand (throughout which permissions are known as)
touchdown display screen
The problem I’m seeing is when the iOS app begins, it briefly flashes the final seen UI display screen BEFORE the native splash display screen. e.g. If I used to be viewing the settings display screen within the app then stop the app, subsequent time I begin the app, the very first thing I see earlier than the native splash display screen is a flash of the settings display screen.
I simply wished to know if anybody has skilled that earlier than, and the way they resolved it.
I’ve googled for hours and can’t discover something comparable, nor can I discover something in flutter documentation. It appears I’ve managed to do one thing in begin up that created this concern that nobody else will get? It’s driving me insane. On condition that this happens previous to native splash, I assume it’s a snapshot explicit to iOS because it doesn’t occur within the android app, and whereas it’s not a breaking concern, it doesn’t look nice from a consumer perspective.
I’ve tried including a white background to cover snapshot, as an alternative I received native splash then white display screen then native splash once more so eliminated the white background.
Added UIApplication.shared.ignoreSnapshotOnNextApplicationLaunch()
to AppDelegate with no change in behaviour
1. Principal.dart
Future fundamental() async {
WidgetsFlutterBinding.ensureInitialized();
tz.initializeTimeZones();
await Future.delayed(const Period(seconds: 2));
await Firebase.initializeApp(choices: DefaultFirebaseOptions.currentPlatform);
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
await Hive.initFlutter();
Hive.registerAdapter(MedicationAdapter()); // TypeID 0
Hive.registerAdapter(DoseEntryAdapter()); // TypeID 1
Hive.registerAdapter(HealthProfessionalAdapter()); // TypeID 2
Hive.registerAdapter(DiscussionItemAdapter()); // TypeID 3
Hive.registerAdapter(AppointmentAdapter()); // TypeID 4
Hive.registerAdapter(UserProfileAdapter()); // TypeID 5
Hive.registerAdapter(TodoItemAdapter()); // TypeID 7
Hive.registerAdapter(DiaryEntryAdapter()); // TypeID 8
Hive.registerAdapter(SelectedTrackersAdapter()); // TypeID 11
Hive.registerAdapter(SelectedGoalsAdapter()); // TypeID 70
Hive.registerAdapter(GpsActivityEntryAdapter()); // TypeID 60
Hive.registerAdapter(WeightEntryAdapter()); // TypeID 50
Hive.registerAdapter(WeightGoalAdapter()); // TypeID 31
Hive.registerAdapter(ActivityGoalAdapter()); // TypeID 28
Hive.registerAdapter(DailyGoalProgressAdapter()); // TypeID 33
Hive.registerAdapter(WeeklyWeightEntryAdapter()); // TypeID 32
Hive.registerAdapter(MenstrualCycleEntryAdapter()); // TypeID 43
Hive.registerAdapter(WorkoutActivityAdapter()); // TypeID 41
Hive.registerAdapter(MeditationGoalAdapter()); // TypeID 98
Hive.registerAdapter(DailyMeditationProgressAdapter()); // TypeID 97
await Hive.openBox('settings');
await Hive.openBox('medicationAlertsSent');
await Hive.openBox('menstrualCycleBox');
await Hive.openBox('gps_activities');
await Hive.openBox('gpsActivity');
await NotificationService.initialize();
await LocalisationSettingsService.init();
// Load saved locale
last savedLocaleCode = LocalisationSettingsService.getSelectedLocale();
last initialLocale = savedLocaleCode != null ? Locale(savedLocaleCode) : null;
runApp(
MultiProvider(
suppliers: [
ChangeNotifierProvider(create: (_) => ThemeNotifier()),
ChangeNotifierProvider(create: (_) => LocaleNotifier()..setLocale(initialLocale)),
],
youngster: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({tremendous.key});
@override
Widget construct(BuildContext context) {
last themeNotifier = Supplier.of(context);
last localeNotifier = Supplier.of(context);
return MaterialApp(
title: 'Pebbl',
navigatorKey: navigatorKey,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: themeNotifier.themeMode,
debugShowCheckedModeBanner: false,
dwelling: const NativeSplashWrapper(),
locale: localeNotifier.locale,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
localeResolutionCallback: (locale, supportedLocales) {
for (var supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == locale?.languageCode &&
supportedLocale.countryCode == locale?.countryCode) {
return supportedLocale;
}
}
return const Locale('en', 'GB');
}
);
}
}
2. Native splash wrapper
import 'package deal:flutter/materials.dart';
import 'package deal:pebbl/screens/login/auth_gate.dart'; // ✅ Now this works
class NativeSplashWrapper extends StatefulWidget {
const NativeSplashWrapper({tremendous.key});
@override
State createState() => _NativeSplashWrapperState();
}
class _NativeSplashWrapperState extends State {
bool _ready = false;
@override
void initState() {
tremendous.initState();
_init();
}
Future _init() async {
await Future.delayed(const Period(milliseconds: 100));
if (mounted) {
setState(() {
_ready = true;
});
}
}
@override
Widget construct(BuildContext context) {
if (!_ready) {
return const Scaffold(
backgroundColor: Coloration(0xFFF5F5F5),
physique: Heart(
youngster: Picture(
picture: AssetImage('property/photos/pebbl_logo.png'),
width: 200,
top: 200,
),
),
);
}
return const AuthGate();
}
}
3. Pubspec.yaml (snippet for flutter native splash)
flutter_native_splash:
coloration: "#F5F5F5"
picture: property/photos/dev_logo.png
fullscreen: true
android: true
ios: true
android_12:
picture: property/photos/android12_dev_logo.png
4. Auth gate
import 'package deal:flutter/materials.dart';
import 'package deal:firebase_auth/firebase_auth.dart';
import 'package deal:pebbl/screens/login/splash_screen.dart';
import 'package deal:pebbl/screens/login/login_screen.dart';
import 'package deal:pebbl/widgets/pebbl_app.dart'; // We’ll extract PebblApp too (see under)
class AuthGate extends StatelessWidget {
const AuthGate({tremendous.key});
@override
Widget construct(BuildContext context) {
return StreamBuilder(
stream: FirebaseAuth.occasion.authStateChanges(),
builder: (context, authSnapshot) {
last isLoggedIn = authSnapshot.hasData;
last display screen = isLoggedIn ? SplashScreen() : const LoginScreen();
return PebblApp(initialScreen: display screen);
},
);
}
}
5. Pebbl app widget
import 'package deal:flutter/materials.dart';
import 'package deal:pebbl/screens/touchdown/landing_screen.dart';
import 'package deal:pebbl/companies/notification_service.dart';
import 'package deal:pebbl/utils/logger.dart';
import 'package deal:pebbl/fundamental.dart'; // for navigatorKey
class PebblApp extends StatefulWidget {
last Widget initialScreen;
const PebblApp({tremendous.key, required this.initialScreen});
@override
State createState() => _PebblAppState();
}
class _PebblAppState extends State with WidgetsBindingObserver {
@override
void initState() {
tremendous.initState();
WidgetsBinding.occasion.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.occasion.removeObserver(this);
tremendous.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
last payload = NotificationService.getAndClearPendingPayload();
if (payload != null) {
Logger.log('App resumed. Routing to payload: $payload');
navigatorKey.currentState?.pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => LandingScreen(payload: payload)),
(route) => false,
);
}
}
}
@override
Widget construct(BuildContext context) {
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
youngster: widget.initialScreen,
);
}
}
6. Splash display screen
import 'package deal:flutter/basis.dart';
import 'package deal:pebbl/utils/logger.dart';
import 'dart:io';
import 'package deal:device_info_plus/device_info_plus.dart';
import 'package deal:flutter/materials.dart';
import 'package deal:firebase_auth/firebase_auth.dart';
import 'package deal:package_info_plus/package_info_plus.dart';
import 'package deal:pebbl/screens/touchdown/landing_screen.dart';
import 'package deal:pebbl/screens/login/login_screen.dart';
import 'package deal:permission_handler/permission_handler.dart';
import 'package deal:pebbl/companies/notification_service.dart';
import 'package deal:geolocator/geolocator.dart';
class SplashScreen extends StatefulWidget {
last String? payloadOverride;
const SplashScreen({tremendous.key, this.payloadOverride});
@override
State createState() => _SplashScreenState();
}
class _SplashScreenState extends State with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation _animation;
String _version = '';
@override
void initState() {
tremendous.initState();
_controller = AnimationController(
period: const Period(seconds: 2),
vsync: this,
)..ahead();
_animation = CurvedAnimation(
dad or mum: _controller,
curve: Curves.easeInOut,
);
_init();
}
Future _init() async {
strive {
last data = await PackageInfo.fromPlatform();
setState(() {
_version = 'v${data.model}+${data.buildNumber}';
});
if (kDebugMode) {
Logger.log('✅ Package deal data loaded');
}
} catch (e) {
if (kDebugMode) {
Logger.log('❌ Package deal data failed: $e');
}
_version = 'v1.0.0';
}
// 🔒 iOS location permission examine
strive {
if (Platform.isIOS) {
last serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (kDebugMode) {
Logger.log('✅ [iOS] Location service enabled: $serviceEnabled');
}
if (serviceEnabled) {
var permission = await Geolocator.checkPermission();
if (kDebugMode) {
Logger.log('✅ [iOS] Location permission standing: $permission');
}
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (kDebugMode) {
Logger.log('✅ [iOS] Location permission requested: $permission');
}
}
}
}
} catch (e) {
if (kDebugMode) {
Logger.log('iOS Location permission request failed: $e');
}
}
// ✅ Android 13+ notification permission
strive {
if (Platform.isAndroid) {
last androidInfo = await DeviceInfoPlugin().androidInfo;
if (kDebugMode) {
Logger.log('✅ Android SDK: ${androidInfo.model.sdkInt}');
}
if (androidInfo.model.sdkInt >= 33) {
last standing = await Permission.notification.request();
if (kDebugMode) {
Logger.log('✅ Notification permission: $standing');
}
if (!standing.isGranted) {
if (kDebugMode) {
Logger.log('Notification permission denied');
}
// Optionally present a dialog right here
}
}
}
} catch (e) {
if (kDebugMode) {
Logger.log('Notification permission request failed: $e');
}
}
// ✅ Android location permissions: foreground and background
strive {
if (Platform.isAndroid) {
// ✅ 1. Examine and request notification permission (Android 13+)
last notifStatus = await Permission.notification.standing;
if (!notifStatus.isGranted) {
if (kDebugMode) {
Logger.log('🔔 Requesting notification permission...');
}
last notifResult = await Permission.notification.request();
if (kDebugMode) {
Logger.log('🔔 Notification permission consequence: $notifResult');
}
}
// ✅ 2. Examine foreground location permission
if (kDebugMode) {
Logger.log('📍 Checking foreground location permission...');
}
var fgStatus = await Permission.location.standing;
if (kDebugMode) {
Logger.log('📍 Foreground location permission standing: $fgStatus');
}
if (!fgStatus.isGranted) {
if (kDebugMode) {
Logger.log('📍 Requesting foreground location permission...');
}
fgStatus = await Permission.location.request();
if (kDebugMode) {
Logger.log('📍 Foreground location permission consequence: $fgStatus');
}
}
// ✅ 3. Examine background location permission (if foreground granted)
if (fgStatus.isGranted) {
if (kDebugMode) {
Logger.log('📍 Checking background location permission...');
}
var bgStatus = await Permission.locationAlways.standing;
if (kDebugMode) {
Logger.log('📍 Background location permission standing: $bgStatus');
}
if (!bgStatus.isGranted) {
if (kDebugMode) {
Logger.log('📍 Requesting background location permission...');
}
bgStatus = await Permission.locationAlways.request();
if (kDebugMode) {
Logger.log('📍 Background location permission consequence: $bgStatus');
}
} else {
if (kDebugMode) {
Logger.log('✅ Background location permission already granted');
}
}
} else {
if (kDebugMode) {
Logger.log('❌ Foreground location denied, skipping background request');
}
}
}
} catch (e) {
if (kDebugMode) {
Logger.log('❗ Permission request failed: $e');
}
}
await Future.delayed(const Period(seconds: 2));
if (!mounted) return;
last consumer = FirebaseAuth.occasion.currentUser;
if (kDebugMode) {
Logger.log('SplashScreen: currentUser = ${consumer?.uid}');
}
last payload = widget.payloadOverride ??
NotificationService.getInitialPayload() ??
NotificationService.consumePendingPayload();
if (kDebugMode) {
Logger.log('SplashScreen init: Firebase consumer: ${consumer?.uid}, payload: $payload');
}
if (kDebugMode) {
Logger.log('SplashScreen: payload resolved = $payload');
}
if (consumer != null) {
strive {
if (kDebugMode) {
Logger.log('Calling rescheduleAllReminders...');
}
await NotificationService.rescheduleAllReminders()
.timeout(const Period(seconds: 5));
if (kDebugMode) {
Logger.log('rescheduleAllReminders accomplished');
}
} catch (e) {
if (kDebugMode) {
Logger.log('rescheduleAllReminders failed or timed out: $e');
}
}
}
if (!mounted) return;
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => consumer == null
? const LoginScreen()
: LandingScreen(payload: payload),
),
);
}
@override
void dispose() {
_controller.dispose();
tremendous.dispose();
}
@override
Widget construct(BuildContext context) {
return Scaffold(
backgroundColor: const Coloration(0xFFF5F5F5),
physique: Column(
mainAxisAlignment: MainAxisAlignment.middle,
youngsters: [
const Spacer(),
FadeTransition(
opacity: _animation,
child: Center(
child: Image.asset(
'assets/images/pebbl_logo.png',
width: 200,
height: 200,
),
),
),
const Spacer(),
Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Text(
_version,
style: const TextStyle(color: Colors.black54),
),
),
],
),
);
}
}
AppDelegate
import UIKit
import Flutter
import UserNotifications
import AVFAudio
import GoogleMaps // 👈 Add this import for Maps
@fundamental
@objc class AppDelegate: FlutterAppDelegate {
//lazy var flutterEngine = FlutterEngine(identify: "my_engine")
override func utility(
_ utility: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
UIApplication.shared.ignoreSnapshotOnNextApplicationLaunch()
// Begin engine (plugins auto-register when the engine is hooked up to the view)
//flutterEngine.run()
// Arrange audio playback class
do {
strive AVAudioSession.sharedInstance().setCategory(
.playback,
mode: .default,
choices: [.mixWithOthers]
)
strive AVAudioSession.sharedInstance().setActive(true)
} catch {
print("Didn't set audio session class.")
}
// ✅ Register Google Maps API key
GMSServices.provideAPIKey("AIzaSyAbnV_HiSaZ2SZaOsIV3l88-yAoYO8Qwd4") // 👈 Change along with your precise key
// Guarantee notification faucets are delivered to Flutter
UNUserNotificationCenter.present().delegate = self
return tremendous.utility(utility, didFinishLaunchingWithOptions: launchOptions)
func applicationWillResignActive(_ utility: UIApplication) {
// Add a white view over the window earlier than backgrounding
let whiteView = UIView(body: window?.bounds ?? .zero)
whiteView.backgroundColor = UIColor.white
whiteView.tag = 999 // So we are able to take away it later
window?.addSubview(whiteView)
}
func applicationDidBecomeActive(_ utility: UIApplication) {
// Take away the white view when app turns into lively
if let whiteView = window?.viewWithTag(999) {
whiteView.removeFromSuperview()
}
}
}
}
SceneDelegate
import UIKit
import Flutter
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
choices connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
//let appDelegate = UIApplication.shared.delegate as! AppDelegate
//let flutterEngine = appDelegate.flutterEngine
let flutterViewController = FlutterViewController()
GeneratedPluginRegistrant.register(with: flutterViewController.engine)
window.rootViewController = flutterViewController
self.window = window
window.makeKeyAndVisible()
}
}