Flutter App Icons and Splash Screens: Complete Setup

Coding Liquids tutorial cover featuring Sagnik Bhattacharya for Flutter app icons and splash screens.
Coding Liquids tutorial cover featuring Sagnik Bhattacharya for Flutter app icons and splash screens.

First impressions matter, and in mobile development, the user experience begins before your first widget even paints to the screen. Leaving the default Flutter logo as your app icon or allowing a jarring white screen to flash while the engine loads makes a project feel unfinished. In this tutorial, we will replace those defaults with custom, professional assets that bridge the gap between the user tapping your app and the first frame rendering.

Follow me on Instagram@sagnikteaches

We will rely on two essential, industry-standard tools to automate this workflow: flutter_launcher_icons and flutter_native_splash. Rather than manually cropping and resizing images for every conceivable iOS and Android density bucket, we will write a declarative configuration that generates all the necessary native files. This includes handling complex modern requirements, such as adaptive icons for Android devices and dark mode variants for your launch screens.

Connect on LinkedInSagnik Bhattacharya

Before diving into the code, you must prepare your high-resolution image assets. Ideally, you should export your designs as 1024x1024 pixel PNG files. Having separate layers for your icon foreground, icon background, and central splash branding will ensure the best possible outcome across all platforms, especially when dealing with the strict layout constraints of the newer Android splash APIs.

Subscribe on YouTube@codingliquids
The Complete Flutter Guide course thumbnail

The Complete Flutter Guide: Build Android, iOS and Web apps

Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.

Enrol now

Preparing Your Image Files

Asset preparation is the most common stumbling block when configuring launch visuals. Both Android and iOS have specific rules about how icons and splash screens should behave, and feeding the generator packages the wrong type of image will result in visual glitches.

For your app icon, prepare a 1024x1024-pixel source. The standard iOS app icon must be opaque: an alpha-bearing icon is not safely “filled with black” and can fail App Store validation. Export app_icon.png with a solid background, or let flutter_launcher_icons remove the alpha channel explicitly as shown below. Separate iOS 18 dark or tinted assets have their own rules and should not be confused with the standard icon.

Android, however, uses Adaptive Icons. These are composed of two layers: a background and a foreground. The operating system then applies a mask (like a circle, rounded square, or teardrop) to these layers. To support this, you should create a separate app_icon_foreground.png with a transparent background, ensuring your main logo sits comfortably within the middle 60% of the canvas so it does not get cropped by aggressive OEM masks.

Place these files in a new directory within your project, such as assets/icon/ and assets/splash/. You do not need to declare these specific generation assets in the regular flutter: assets: section of your pubspec, as they are only read during the build step.

Adding flutter_launcher_icons to Your Project

To generate the app icons, we will add the flutter_launcher_icons package. Because this tool only generates files and is not required by your app at runtime, it should be added to your dev_dependencies.

Open your terminal and run the following command to fetch the latest version:

flutter pub add dev:flutter_launcher_icons

Next, open your pubspec.yaml file. We need to append a new configuration block at the root level of the file (not nested under flutter:). This block tells the package where to find your source images and which platforms to target.

flutter_launcher_icons:
  android: "launcher_icon"
  ios: true
  image_path: "assets/icon/app_icon.png"
  min_sdk_android: 21
  remove_alpha_ios: true
  
  # Adaptive icon configuration for modern Android
  adaptive_icon_background: "#ffffff" # Can be a hex color or an image path
  adaptive_icon_foreground: "assets/icon/app_icon_foreground.png"
  
  # Optional: Web support
  web:
    generate: true
    image_path: "assets/icon/app_icon.png"
    background_color: "#ffffff"
    theme_color: "#ffffff"

In this configuration, ios: true tells the package to replace the default iOS icon set. For Android, setting the value to "launcher_icon" names the generated files correctly for the Android manifest. We supply the flat image for iOS and fallback Android versions via image_path, while supplying the layered assets via the adaptive_icon keys.

Generating the App Icons

With the configuration saved, you can now instruct the package to process your images and overwrite the default Flutter boilerplate.

dart run flutter_launcher_icons

When you run this command, the package reads your high-resolution PNGs and scales them down into the various sizes required by the platforms. For iOS, it updates the ios/Runner/Assets.xcassets/AppIcon.appiconset directory. For Android, it populates the various mipmap folders inside android/app/src/main/res/ and generates the XML files required for adaptive layers.

If you ever change your app icon in the future, simply replace the source files in your assets/icon/ folder and re-run this command.

The Complete Flutter Guide course thumbnail

The Complete Flutter Guide: Build Android, iOS and Web apps

Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.

Enrol now

Why You Need a Native Splash Screen

A common mistake newer developers make is building a standard Flutter widget to act as a splash screen, usually placed as the first route in their MaterialApp. While this allows for complex animations, it does not solve the fundamental problem: the white screen of death.

When a user taps your app icon, the operating system has to load the Flutter engine into memory before Dart code can execute. During this brief window (which can last a few seconds on older devices), Flutter cannot draw anything. The OS displays whatever is configured in the native Android and iOS project files. By default, this is a blank white screen.

To provide a seamless launch experience, we must configure the native splash screens. We will use the flutter_native_splash package to inject our branding directly into the Android and iOS platform files, ensuring our logo appears the instant the app is tapped.

Configuring flutter_native_splash

Just like the icon generator, this package modifies native project files and belongs in your development dependencies.

flutter pub add dev:flutter_native_splash

Add a new configuration block to your pubspec.yaml. We will configure a background colour, a central logo, and specify alternative assets for when the user's device is in dark mode.

flutter_native_splash:
  # The background color of the splash screen
  color: "#ffffff"
  # The central image (should be a PNG with transparency)
  image: "assets/splash/splash_logo.png"
  
  # Dark mode variants
  color_dark: "#121212"
  image_dark: "assets/splash/splash_logo_dark.png"
  
  # Target platforms
  android: true
  ios: true
  web: false

Your splash_logo.png should ideally not occupy the entire screen. A logo roughly 300x300 pixels works best, as the package will centre it on the screen regardless of the device's aspect ratio.

Taming the Android 12 Splash Screen API

Starting with Android 12 (API 31), Google fundamentally changed how splash screens operate at the system level. The OS now enforces a standard animation: it grabs your app icon, places it in a circle in the centre of the screen, and transitions to your app.

If you ignore this change, Android 12 users will see a generic, cropped version of your app icon instead of the splash screen you carefully designed. Fortunately, flutter_native_splash supports this new API via the android_12 block.

Append the following to your flutter_native_splash configuration:

  android_12:
    # The image must fit within a circular mask
    image: "assets/splash/android12_logo.png"
    icon_background_color: "#ffffff"
    
    # Dark mode variants for Android 12
    image_dark: "assets/splash/android12_logo_dark.png"
    icon_background_color_dark: "#121212"

The image you provide for android_12 must conform to strict dimensions. It is placed inside a circular mask taking up roughly 288x288 dp. If your logo touches the edges of your PNG, it will be heavily cropped by the system. Ensure your logo is scaled down within the canvas, leaving ample transparent padding around it.

Generating and Preserving the Splash Assets

With both the standard and Android 12 configurations in place, run the generator command:

dart run flutter_native_splash:create

This command updates your Android styles.xml, modifies the iOS LaunchScreen.storyboard, and copies your images into the correct native resource folders.

By default, the native splash screen disappears the exact moment the Flutter engine finishes initialising. However, your app might not be ready to show its UI yet. You might need to establish a WebSocket connection, fetch a user token from secure storage, or initialise Firebase.

To prevent a jarring transition where the splash screen vanishes only to be replaced by a Flutter loading spinner, you can tell the package to preserve the native splash screen until your asynchronous setup is complete.

Update your main.dart to hook into the binding process:

import 'package:flutter/material.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';

void main() async {
  // 1. Initialize the binding required to interact with the engine
  WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
  
  // 2. Tell the native platform to hold the splash screen
  FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);

  // 3. Perform your async initialization (e.g., database, API setup)
  await initialiseAppServices();

  // 4. Run your app
  runApp(const MyApp());
}

Future<void> initialiseAppServices() async {
  // Simulating a network request or database load
  await Future.delayed(const Duration(seconds: 2));
  
  // 5. Remove the splash screen once the app is ready
  FlutterNativeSplash.remove();
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Splash Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const Scaffold(
        body: Center(
          child: Text('App loaded successfully!'),
        ),
      ),
    );
  }
}

Using FlutterNativeSplash.preserve() ensures a perfectly smooth transition. The user sees your branded native splash screen for the duration of the engine load plus however long your Dart-level initialisation takes, before seamlessly cross-fading to your first Flutter frame.

Fixing iOS Caching and Common Pitfalls

When testing your new icons and splash screens, you will almost certainly run into caching issues, particularly on iOS. The iOS operating system aggressively caches the LaunchScreen.storyboard to speed up app launches. If you generate a new splash screen and rebuild the app, the simulator or physical iPhone will often stubbornly display the old white screen or a previous design.

To force iOS to clear this cache and show your updated splash screen, you must completely uninstall the app from the device or simulator. Then, turn the device off and on again (or use Device > Restart in the simulator menu). Rebuild the app from Xcode or your IDE, and the new assets will appear.

Another common mistake is forgetting to commit the generated native files to source control. The flutter_launcher_icons and flutter_native_splash packages modify files inside the android/ and ios/ directories. These modifications are the actual result of the tools. Ensure you commit these changes to Git so your continuous integration pipelines and teammates have the correct visual assets without needing to manually run the generator scripts.

Finally, always test your app in both light and dark modes on physical devices. Emulators can sometimes mask subtle colour mismatches between your provided hex codes and the actual rendered background. By verifying on real hardware, you ensure your app delivers a polished, professional launch experience every time.

Further reads

Keep going with these related tutorials from this site.