How Flutter Resolves Image Assets
Flutter uses a device pixel ratio (DPR) system to select the sharpest image for the current screen. When you reference an asset with Image.asset('assets/images/logo.png'), the framework inspects the running device's DPR and looks for a variant in the matching numbered subdirectory. A phone with a 3.0 DPR will load the file from the 3.0x/ folder; if that exact variant does not exist, Flutter falls back to the closest available resolution.
This mechanism is conceptually identical to Android's density buckets and iOS's @2x / @3x naming convention, but Flutter uses numbered directories instead of filename suffixes. If you are new to density-independent pixels, read What is DPI? and dp vs px vs pt for background.
Directory Structure
The recommended layout places the baseline (1x) image in the root assets/images/ directory and higher-density variants in numbered subdirectories:
assets/
images/
logo.png # 1x baseline (e.g. 48 x 48 px)
1.5x/
logo.png # 1.5x variant (72 x 72 px)
2.0x/
logo.png # 2x variant (96 x 96 px)
3.0x/
logo.png # 3x variant (144 x 144 px)
4.0x/
logo.png # 4x variant (192 x 192 px) Flutter recognises the subdirectory names automatically. You only need to declare the base path in pubspec.yaml; the engine discovers every numbered variant at build time.
Declaring Assets in pubspec.yaml
Every asset must be registered in the flutter section of pubspec.yaml. You can list individual files or entire directories:
flutter:
assets:
# Individual file — density variants discovered automatically
- assets/images/logo.png
# Entire directory — every file in images/ is bundled
- assets/images/ When you reference a directory, Flutter includes every file directly inside it, but does not recurse into subdirectories unless you list them explicitly. The numbered variant folders (2.0x/, 3.0x/, etc.) are a special case: Flutter always scans them for density variants even without explicit declarations.
Loading Images in Code
Flutter provides two primary ways to display an asset image. The most common is the Image.asset() widget:
Image.asset(
'assets/images/logo.png',
width: 48,
height: 48,
fit: BoxFit.contain,
) Under the hood, Image.asset() creates an AssetImage provider. You can also use AssetImage directly when you need to pass an image provider to a decoration or other widget:
Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/background.png'),
fit: BoxFit.cover,
),
),
) In both cases, Flutter uses the current MediaQuery.devicePixelRatioOf(context) to pick the correct density variant at runtime.
Flutter Directories vs Android and iOS
The following table maps Flutter's numbered variant directories to their Android and iOS equivalents. This is useful when converting an existing native project to Flutter or when communicating with designers who work in platform-specific terminology.
| Flutter directory | Scale | Android bucket | iOS suffix |
|---|---|---|---|
| (root) | 1x | mdpi | @1x |
| 1.5x/ | 1.5x | hdpi | — |
| 2.0x/ | 2x | xhdpi | @2x |
| 3.0x/ | 3x | xxhdpi | @3x |
| 4.0x/ | 4x | xxxhdpi | @4x |
Platform App Icons with flutter_launcher_icons
App icons follow platform-specific rules that differ from in-app image assets. Android requires adaptive icons with separate foreground and background layers, while iOS expects a set of fixed-size PNGs. The flutter_launcher_icons package automates this entirely.
Add the package to your dev dependencies and create a configuration section in pubspec.yaml:
dev_dependencies:
flutter_launcher_icons: ^0.14.3
flutter_launcher_icons:
android: true
ios: true
image_path: "assets/icon/app_icon.png"
adaptive_icon_background: "#FFFFFF"
adaptive_icon_foreground: "assets/icon/app_icon_foreground.png"Then run the generator:
dart run flutter_launcher_iconsThe tool generates every required size for both platforms and places them in the correct native directories. Provide a source icon of at least 1024 x 1024 px for the best results.
Best Practices for Flutter Assets
Following a few conventions will keep your asset pipeline clean and your app bundle small.
- Use SVG where possible. The
flutter_svgpackage renders vector files at any resolution without density variants. This is ideal for icons, illustrations, and logos. See PNG vs WebP vs SVG for a format comparison. - Organise by feature, not by density. Group related images under feature-specific directories (e.g.
assets/images/onboarding/,assets/images/profile/) so that each feature folder contains its own density subdirectories. - Compress PNGs before bundling. Tools like
pngquantoroptipngcan reduce file size by 40-70% with no visible quality loss. Every kilobyte saved is multiplied across four or five density variants. Read more about reducing app size through image optimisation. - Consider WebP for photos. Flutter supports WebP natively, and it typically produces files 25-35% smaller than equivalent PNGs with comparable quality.
- Start from the highest density. Always design or export at 4x and scale down. Upscaling a low-resolution source adds no real detail. Use a tool like App Image Kit to generate all density variants from a single high-resolution source automatically.
Performance: Caching and Memory
Flutter caches decoded images in a global ImageCache that holds up to 1000 images or 100 MB by default. This means the same asset loaded in multiple widgets is decoded only once. However, large images can quickly consume this budget.
For images that appear frequently or on the first visible screen, use precacheImage() to decode them before the widget tree needs them. This eliminates the single-frame flicker that can occur when an image loads asynchronously:
@override
void didChangeDependencies() {
super.didChangeDependencies();
precacheImage(
const AssetImage('assets/images/hero_banner.png'),
context,
);
}Keep the following memory considerations in mind:
- A decoded RGBA image consumes width x height x 4 bytes in memory regardless of the original file format. A 1080 x 1920 image uses roughly 8 MB decoded.
- Avoid loading images larger than the widget that displays them. Set explicit
cacheWidthandcacheHeightparameters onImage.asset()to decode at a smaller size and save memory. - For long scrollable lists with many images, consider using
ListView.builderso that off-screen images are garbage collected. - If your image cache grows too large, you can tune the limits via
PaintingBinding.instance.imageCache.maximumSizeBytes.
Summary
Flutter's asset resolution system is straightforward once you understand the numbered-directory convention. Declare your base assets in pubspec.yaml, place density variants in 2.0x/, 3.0x/, and 4.0x/ subdirectories, and the framework handles the rest at runtime. Use SVG via flutter_svg for resolution-independent icons, compress raster images before bundling, and precache critical images for a smooth first-frame experience.
To generate every density variant from a single source image without manual resizing, try App Image Kit — it runs entirely in your browser and exports a ready-to-use ZIP with all the variants your Flutter project needs.
Ready to export your assets?
App Image Kit generates all density variants from a single source image — free, private, and instant.