skip to Main Content

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


  1. There are 3 steps you should consider in order to reduce loading time.

    1. Find out which calls are necessery to be there on your initial loading (meaning what the user sees on the first screen), and move the rest in a lateInit function.
    2. Use async functions when possible, in order not to delay the process.
    3. Fist time loading may be longer, but after that you can "cache" specific things and speed up the loading time for next 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.

    Login or Signup to reply.
  2. My home screen opens up quickly, but after that the data loads up from firebase in a list view, tab view and grid view. This data loading takes up most of the time.

    1. Are you loading all the data from the DB ? If yes, you will have to use Skip / Limit
    2. The initializing Google AdMob should be unawaited since the app loading up should not depend on whether the ads get loaded or not.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search