skip to content

In Flutter, how are 2.0x and 3.0x image asset variants chosen for a device, and what must pubspec.yaml declare?

level: middleimportance: must knowfreq 46%

answer

  1. folders named 2.0x, 3.0x
  2. declare only the main asset
  3. below 2.0: round up
  4. otherwise nearest, clamped at the ends
  5. drawn at the main asset's logical size

basics

~20 s

Put higher-resolution copies in sibling folders such as 2.0x/ and 3.0x/ and declare only the main asset or its directory; Flutter bundles the variants, and AssetImage picks the one closest to the device pixel ratio, rendering it at the main asset's logical size.

solid answer

~40 s

Variants share the main file's name inside folders named after a pixel ratio: `badges/history.png`, `badges/2.0x/history.png`, `badges/3.0x/history.png`. `pubspec.yaml` lists only the main asset or `badges/`, and the tool bundles every variant it finds. At runtime `AssetImage`, which `Image.asset` uses, takes an exact match if there is one; below the lowest variant it uses the lowest and above the highest the highest. Between two, a device under 2.0 gets the higher one, because upscaling artefacts show more on big pixels, and otherwise the nearest. The chosen image is scaled to the main asset's logical size, so a 216-pixel 3.0x file draws in 72 logical pixels. Loading bytes with `rootBundle.load` gets the main file only.

code

yaml · 3 lines
yaml
flutter:
  assets:
    - assets/badges/

go deeper

for a junior

Know the folder convention, 2.0x and 3.0x next to the main file, and that pubspec.yaml declares only the main asset or its directory.

for a middle

Explain the selection rules, including the round-up rule below 2.0 and clamping at the ends, and why the image keeps the main asset's logical size.

for a senior

Decide which ratios to ship to balance sharpness against bundle size, spot blurry or oversized variants, and know when vectors replace raster variants.

for a principal

Set asset-pipeline standards for designers and engineers so variants are generated consistently rather than exported by hand.

## Device pixel ratio in one paragraph Flutter lays out in **logical pixels**. The **device pixel ratio** (DPR) says how many physical pixels one logical pixel covers: about 1.0 on an old low-density screen, 2.0 to 3.5 on modern phones. A 72 × 72 logical-pixel badge needs 144 × 144 physical pixels at DPR 2.0 and 216 × 216 at DPR 3.0 to look sharp. **Resolution-aware assets** let you ship several copies and have Flutter pick one per device. ## The folder layout Variants sit in folders named after the ratio they target, next to the **main asset**: ```text assets/badges/history.png main asset, treated as 1.0 assets/badges/2.0x/history.png variant for DPR 2.0 assets/badges/3.0x/history.png variant for DPR 3.0 ``` - The folder name is a number followed by `x`; `2.0x` is the documented form, and `2x` parses too. - The file name must match the main asset's exactly. - Each variant should show the same content at a proportionally larger pixel size. ## What pubspec.yaml declares You declare **only the main asset**, or the directory that holds it: ```yaml flutter: assets: - assets/badges/ ``` The `flutter` tool finds the `2.0x/` and `3.0x/` variants and bundles them. This is the one exception to the rule that a directory entry does not include subdirectories. The main file may even be absent: the entry must still be declared, and devices below the lowest variant then fall back to that lowest variant. ## How AssetImage chooses At runtime, `AssetImage` (the provider behind `Image.asset` when no `scale` is given) reads the asset manifest, collects the candidates for the key, and applies these rules in order: 1. A variant whose ratio **equals** the device's DPR wins. 2. If the DPR is **below the lowest** candidate, the lowest is used; if it is **above the highest**, the highest is used. 3. Between two candidates, a **low-density screen (DPR under 2.0)** gets the **higher** one, because upscaling artefacts are more visible on large physical pixels. 4. Otherwise the **nearest** candidate wins; below the midpoint the lower one is chosen. With a main asset, `2.0x` and `3.0x`: | Device DPR | Chosen | |---|---| | 1.0 | main asset | | 1.5 | 2.0x (low-DPR rule rounds up) | | 2.0 | 2.0x (exact) | | 2.4 | 2.0x (nearest) | | 2.7 | 3.0x (nearest) | | 3.5 | 3.0x (highest available) | The DPR comes from the nearest `MediaQuery`; without one it is taken as 1.0 and the main asset is used. ## Rendering at the right size The chosen image is tagged with its scale, so when `Image` has no explicit width or height it occupies the **main asset's logical size**. The 216 × 216 file in `3.0x/` draws into 72 × 72 logical pixels, just with more detail. Passing an explicit `scale` to `Image.asset` switches to `ExactAssetImage`, which skips variant selection and uses the named file at that scale. ## Choosing which ratios to ship Every variant is a separate file in the bundle, so three ratios roughly triple the space a raster image takes. Useful rules of thumb: - Ship the ratios your target devices actually report; most current phones sit between 2.0 and 3.5, so `2.0x` and `3.0x` cover them well. - Keep a main asset for the web and desktop, where ratios around 1.0 are common, or omit it and let the lowest variant serve those screens. - Selection considers **only the pixel ratio**. The source notes that locale, text direction, size and platform are not used, so a right-to-left or language-specific image needs its own key. ## Where variants do not apply - **`rootBundle.load(key)`** returns the bytes of exactly that key, the main file; only the image providers resolve variants. - **Non-image data** such as a JSON question pack gains nothing from ratio folders. - **Vector formats** scale without variants; they are another tool entirely. ## Common mistakes - Declaring every variant path by hand: harmless but noisy, and easy to get out of sync. - Naming a folder `2.0` or `x2`, without a number followed by a trailing `x`, which the tool does not recognise as a variant folder. - Making the 2.0x file the same pixel size as the main one, which looks blurry and defeats the purpose.

  • What does a device with a pixel ratio of 1.3 get when only the main asset and a 2.0x variant exist?
    The 2.0x variant. The ratio lies between 1.0 and 2.0, and because it is below 2.0 the low-density rule picks the higher candidate instead of the nearest, since upscaling a 1.0 image would show artefacts on large physical pixels. The image is still drawn at the main asset's logical size.
  • Does rootBundle.load('assets/badges/history.png') return the 3.0x file on a 3.0 device?
    No. `load` returns the bytes of exactly the key you pass, which is the main asset. Variant selection happens only in the image providers such as `AssetImage`, which consult the asset manifest and the device pixel ratio. To load a specific variant's bytes, pass its own key, such as `assets/badges/3.0x/history.png`.

saying these in an interview costs you the question

  • Every 2.0x and 3.0x file must be listed separately in pubspec.yaml.
  • Flutter always picks the next higher variant, whatever the pixel ratio.
  • A 3.0x image renders three times larger on screen than the main asset.
  • rootBundle.load also resolves resolution variants automatically.
  • The main asset file must exist or the build fails.