My app is already live on the Play Store, but it takes almost 15-20 seconds to fully open and get to the home screen.
I am using Firebase for backend with my flutter app.
I have included step by step code that executes when my app starts.
main.dart
AndroidNotificationChannel channel = const AndroidNotificationChannel(
'high_importance_channel',
'High Importance Notifications',
description: 'This channel is used for high importance notifications',
importance: Importance.max,
playSound: true,
);
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Firebase App
await Firebase.initializeApp();
// Activate Firebase App Check
await FirebaseAppCheck.instance.activate(
androidProvider: AndroidProvider.playIntegrity,
);
// Initialize Google Mobile Ads
await MobileAds.instance.initialize();
// Set preferred device orientation
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
// Initialize Flutter Local Notifications Plugin
// FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
// Set foreground notification presentation options
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => AppNavigationProvider(),
),
ChangeNotifierProvider(
create: (_) => SellerFormProvider(),
),
ChangeNotifierProvider(
create: (_) => LocationProvider(),
),
],
child: const MyApp(),
),
);
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
registerMessaging();
}
void registerMessaging() async {
await compute(_registerMessaging, null);
}
static Future<void> _registerMessaging(void _) async {
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
final RemoteNotification? notification = message.notification;
final AndroidNotification? android = message.notification?.android;
if (notification != null && android != null) {
flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
channelDescription: channel.description,
playSound: true,
importance: Importance.max,
priority: Priority.high,
color: greenColor,
icon: '@mipmap/ic_launcher',
),
),
);
}
});
}
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'App name',
color: blueColor,
themeMode: ThemeMode.light,
theme: ThemeData(fontFamily: 'Rubik'),
debugShowCheckedModeBanner: false,
home: const LoadingScreen(),
onUnknownRoute: (RouteSettings settings) {
return MaterialPageRoute(
settings: settings,
builder: (BuildContext context) => const ErrorScreen(),
);
},
);
}
}
loading_screen.dart
class LoadingScreen extends StatefulWidget {
const LoadingScreen({Key? key}) : super(key: key);
@override
State<LoadingScreen> createState() => _LoadingScreenState();
}
class _LoadingScreenState extends State<LoadingScreen> {
@override
void initState() {
super.initState();
//check whether user is logged in or not. Then navigate her accordingly.
FirebaseAuth.instance.authStateChanges().listen((User? user) {
if (user != null) {
Get.offAll(() => const MainScreen(selectedIndex: 0));
} else {
Get.offAll(() => const LandingScreen());
}
});
}
@override
Widget build(BuildContext context) {
return const Scaffold(
backgroundColor: whiteColor,
body: Center(
child: CustomLoadingIndicator(),
),
);
}
}
main_screen.dart
class MainScreen extends StatefulWidget {
final int selectedIndex;
const MainScreen({super.key, required this.selectedIndex});
@override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
final FirebaseServices _services = FirebaseServices();
final User? user = FirebaseAuth.instance.currentUser;
late StreamSubscription<ConnectivityResult> subscription;
bool isDeviceConnected = false;
bool isAlertSet = false;
@override
void initState() {
super.initState();
getConnectivity();
}
showNetworkError() {
showModalBottomSheet(
context: context,
backgroundColor: transparentColor,
isDismissible: false,
enableDrag: false,
isScrollControlled: false,
builder: (context) {
return SafeArea(
child: Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
),
color: whiteColor,
),
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom + 15,
left: 15,
right: 15,
top: 15,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Center(
child: Text(
'Network Connection Lost',
style: GoogleFonts.interTight(
fontSize: 20,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
),
const SizedBox(
height: 15,
),
Image.asset(
'assets/no-network.png',
fit: BoxFit.contain,
semanticLabel: 'no network connection',
width: MediaQuery.of(context).size.width * 0.8,
height: MediaQuery.of(context).size.height * 0.2,
),
const SizedBox(
height: 15,
),
Container(
padding: const EdgeInsets.all(15),
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: greyColor,
),
child: Text(
'Please check your internet connection',
textAlign: TextAlign.center,
maxLines: 2,
softWrap: true,
overflow: TextOverflow.ellipsis,
style: GoogleFonts.interTight(
fontSize: 15,
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(
height: 10,
),
CustomButtonWithoutIcon(
text: 'Re-Connect',
onPressed: () async {
Get.back();
setState(() {
isAlertSet = false;
});
isDeviceConnected =
await InternetConnectionChecker().hasConnection;
if (!isDeviceConnected) {
showNetworkError();
setState(() {
isAlertSet = true;
});
}
},
borderColor: redColor,
bgColor: redColor,
textIconColor: whiteColor,
),
],
),
),
);
},
);
}
Future<void> getConnectivity() async {
await for (final _ in Connectivity().onConnectivityChanged) {
isDeviceConnected = await InternetConnectionChecker().hasConnection;
if (!isDeviceConnected && !isAlertSet) {
showNetworkError();
setState(() => isAlertSet = true);
}
}
}
Future<void> onSellButtonClicked() async {
final userData = await _services.getCurrentUserData();
if (userData['location'] != null) {
Get.to(
() => const SellerCategoriesListScreen(),
);
} else {
Get.to(() => const LocationScreen(isOpenedFromSellButton: true));
showSnackBar(
content: 'Please set your location to sell products',
color: redColor,
);
}
}
@override
void dispose() {
subscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Consumer2<LocationProvider, AppNavigationProvider>(
builder: (context, locationProv, mainProv, child) {
final int selectedIndex = mainProv.currentPageIndex;
final List<Widget> pages = [
HomeScreen(
locationData: locationProv.locationData,
),
const MyChatsScreen(),
const MyFavoritesScreen(),
const MyProfileScreen(),
];
const List<IconData> iconsList = [
MdiIcons.homeOutline,
MdiIcons.chatOutline,
MdiIcons.heartOutline,
MdiIcons.accountCircleOutline,
];
const List<IconData> selectedIconsList = [
MdiIcons.home,
MdiIcons.chat,
MdiIcons.heart,
MdiIcons.accountCircle,
];
const List<String> titlesList = [
'Explore',
'Chats',
'Favorites',
'Account',
];
void onItemTapped(int index) {
mainProv.switchToPage(index);
}
void onFloatingActionButtonPressed() {
if (!user!.emailVerified &&
user!.providerData[0].providerId == 'password') {
Get.to(() => const EmailVerificationScreen());
} else {
onSellButtonClicked();
}
}
return Scaffold(
backgroundColor: whiteColor,
body: IndexedStack(
index: selectedIndex,
children: pages,
),
floatingActionButton: FloatingActionButton(
backgroundColor: blueColor,
elevation: 0,
tooltip: 'List a product',
enableFeedback: true,
onPressed: onFloatingActionButtonPressed,
child: const Center(
child: Icon(
MdiIcons.plus,
size: 35,
),
),
),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: AnimatedBottomNavigationBar.builder(
onTap: onItemTapped,
gapLocation: GapLocation.center,
activeIndex: selectedIndex,
backgroundColor: greyColor,
elevation: 5,
height: 55,
leftCornerRadius: 10,
rightCornerRadius: 10,
notchSmoothness: NotchSmoothness.defaultEdge,
splashColor: transparentColor,
itemCount: pages.length,
tabBuilder: (index, isActive) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
selectedIndex == index
? selectedIconsList[index]
: iconsList[index],
size: 24,
color: blackColor,
),
AutoSizeText(
titlesList[index],
maxLines: 1,
style: TextStyle(
fontSize: 12,
fontWeight: selectedIndex == index
? FontWeight.w600
: FontWeight.w500,
color: blackColor,
),
)
],
);
},
),
);
},
);
}
}
home_screen.dart
class HomeScreen extends StatefulWidget {
final LocationData? locationData;
const HomeScreen({
super.key,
this.locationData,
});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen>
with SingleTickerProviderStateMixin {
late TabController tabBarController;
final _services = FirebaseServices();
final User? user = FirebaseAuth.instance.currentUser;
String area = '';
String city = '';
String state = '';
bool isLocationEmpty = false;
bool isLoading = true;
late DateTime currentBackPressTime;
@override
void initState() {
super.initState();
tabBarController = TabController(
length: 3,
vsync: this,
);
_getCurrentUserData();
}
void _getCurrentUserData() async {
final value = await _services.getCurrentUserData();
if (value['location'] == null) {
_getEmptyLocationUI();
} else {
_getAddressToUI(value);
}
setState(() {
isLoading = false;
});
}
void _getAddressToUI(DocumentSnapshot<Object?> value) {
if (mounted) {
setState(() {
area = value['location']['area'];
city = value['location']['city'];
state = value['location']['state'];
});
}
}
_getEmptyLocationUI() {
if (mounted) {
setState(() {
isLocationEmpty = true;
tabBarController.index = 1;
});
}
}
@override
void dispose() {
tabBarController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: whiteColor,
body: AppBody(),
);
}
}
I have tried everything to my knowledge, but still the launch time isn’t getting shortened.
Reducing the time by just 5-10% would make a huge difference in user retention rate of my app.
Please help.
2
Answers
There are 3 steps you should consider in order to reduce loading time.
In your case, you might had a "light" Homescreen, that loads first with only the information needed. Binance app is a good example, which loads the homepage and later you get the values of your account or other ratios.
unawaited
since the app loading up should not depend on whether the ads get loaded or not.