skip to content

In Flutter, how do Image.asset, Image.network, Image.file and Image.memory differ, and what do all four have in common?

level: juniorimportance: must knowfreq 66%

answer

  1. each wraps an ImageProvider
  2. AssetImage, NetworkImage, FileImage, MemoryImage
  3. one shared in-memory ImageCache
  4. network: no disk cache; CORS on web
  5. same builders and cacheWidth on all

basics

~20 s

Each constructor wraps a different ImageProvider: AssetImage for bundled files, NetworkImage for URLs, FileImage for device files and MemoryImage for bytes. All decode through the same in-memory ImageCache and accept cacheWidth, cacheHeight, the loading, frame and error builders, and gaplessPlayback.

solid answer

~40 s

`Image` is one widget; the named constructors only choose its `ImageProvider`. `Image.asset` uses `AssetImage` (or `ExactAssetImage` when you pass `scale`) and resolves resolution variants from the app bundle. `Image.network` uses `NetworkImage`, which on mobile and desktop issues an HTTP GET through a shared `dart:io` `HttpClient`, sends optional `headers`, and fails with `NetworkImageLoadException` on a non-200 status. `Image.file` uses `FileImage` and reads a `dart:io` `File`, so it is unavailable on the web. `Image.memory` uses `MemoryImage` over a `Uint8List`. All four share the global `ImageCache`, which holds decoded images in memory only: `NetworkImage` writes nothing to disk, so a cold start downloads the photo again.

code

dart · 19 lines
dart
import 'package:flutter/material.dart';

class ListingPhoto extends StatelessWidget {
  const ListingPhoto({super.key, required this.url, required this.token});

  final String url;
  final String token;

  @override
  Widget build(BuildContext context) {
    return Image.network(
      url,
      headers: {'Authorization': 'Bearer $token'},
      fit: BoxFit.cover,
      errorBuilder: (context, error, stackTrace) =>
          Image.asset('assets/images/listing_placeholder.png', fit: BoxFit.cover),
    );
  }
}

go deeper

for a junior

Name the four constructors, their sources and providers, and know that errorBuilder and a placeholder are expected for network images.

for a middle

Explain that every constructor goes through an ImageProvider key and the shared in-memory ImageCache, and what that means for repeat displays and restarts.

for a senior

Account for platform differences, such as web CORS, ignored cacheWidth on web and no disk cache, when choosing how a photo-heavy app loads images.

for a principal

Decide where image caching lives in the app's architecture, in memory, on disk or at a CDN, and which layer owns each concern.

## One widget, four providers `Image` is a single `StatefulWidget` whose job is to display whatever an **`ImageProvider`** produces. The four named constructors differ only in which provider they create: | Constructor | Provider | Source | Cache key built from | |---|---|---|---| | `Image.asset(name)` | `AssetImage` (`ExactAssetImage` if `scale` is given) | the app's asset bundle | bundle, asset name, chosen variant | | `Image.network(url)` | `NetworkImage` | an HTTP(S) URL | URL, scale, headers | | `Image.file(file)` | `FileImage` | a `dart:io` `File` on the device | file path, scale | | `Image.memory(bytes)` | `MemoryImage` | a `Uint8List` already in memory | the very same `Uint8List` instance, scale | An `ImageProvider` knows how to produce a **key** describing the exact image, and how to load and decode the bytes for that key. The widget asks the provider to **resolve** the image, and the provider goes through the global **`ImageCache`** first: if a decoded image for the same key is already there, it is reused; if not, loading starts and the result is cached. ## What each one does differently - **`Image.asset`** reads from `DefaultAssetBundle.of(context)`, so it follows a substituted bundle, and picks the resolution variant (`2.0x`, `3.0x`) closest to the device pixel ratio. Assets from packages take a `package` argument. - **`Image.network`** fetches the URL. On mobile and desktop it uses one shared `HttpClient` from `dart:io`, adds any `headers` you pass (for example an authorization header for private listing photos), reports download progress to `loadingBuilder`, and throws `NetworkImageLoadException` with the status code when the server does not answer 200. A failed load is evicted from the cache so the next attempt tries the network again. On Android, release builds need the internet permission in the manifest. - **`Image.file`** reads a local file, typically a photo the user just took. `dart:io` files do not exist on the web. - **`Image.memory`** decodes bytes you already hold, for example a thumbnail embedded in an API response. ## What they share All four constructors accept the same display and loading options: - `width`, `height`, `fit`, `alignment` for layout and painting; - **`cacheWidth` / `cacheHeight`**, which make the engine decode at a smaller size to save memory; - **`frameBuilder`**, **`loadingBuilder`** (useful mainly for network images) and **`errorBuilder`** for placeholders, progress and fallbacks; - **`gaplessPlayback`**, which keeps the old image visible while a new provider loads; - `filterQuality`, defaulting to `FilterQuality.medium`. ## Deferred loading while scrolling Every `Image` wraps its provider in a **`ScrollAwareImageProvider`** before resolving it. If the image is already in the cache, it resolves at once. Otherwise, while the enclosing scrollable is moving at high velocity, it waits frame by frame and starts loading only when scrolling slows, and it gives up if the widget has been disposed by then. A fast fling through a long gallery therefore does not start a download for every photo that flashes past. You get this for free with any of the four constructors. ## The cache is memory only Flutter's `ImageCache` holds **decoded** images in memory, by default up to 1000 entries and 100 MiB. It does not persist anything. `NetworkImage` downloads the bytes, decodes them and keeps only the decoded image in that cache, so: 1. Scrolling back to a photo seen a minute ago is instant, if the cache has not evicted it. 2. After the app restarts, or after a memory-pressure clear, the same photo is downloaded again. 3. Disk caching for a real-estate gallery, where users revisit listings, needs a separate caching layer or package on top. ## Platform notes for Image.network on the web - The browser's cross-origin rules apply: an image host that does not allow the app's origin makes the fetch fail. - `webHtmlElementStrategy` (default `WebHtmlElementStrategy.never`) can display such images through an HTML element instead, at the cost of performance and of options like `headers`, `opacity` and filtering. - `cacheWidth` and `cacheHeight` are ignored for network images on the web, because the browser does the decoding. ## Choosing one | Situation | Constructor | |---|---| | Logo, placeholder, onboarding art shipped with the app | `Image.asset` | | Listing photos from the server | `Image.network` | | A photo the agent just took with the camera | `Image.file` | | A thumbnail delivered as bytes inside JSON | `Image.memory` |

  • Why does Image.network download a listing photo again after the app restarts?
    `NetworkImage` keeps only the decoded image in the in-memory `ImageCache`; it never writes the downloaded bytes to disk. When the process ends, or the cache is cleared under memory pressure, the image is gone and the next display fetches it again. Persisting photos across launches needs a separate disk-caching layer.
  • What happens to the cache entry when Image.network gets a 404?
    `NetworkImage` throws a `NetworkImageLoadException` carrying the status code, and it evicts the key from the `ImageCache`, so a later attempt, such as a retry button rebuilding the image, goes back to the network instead of replaying the cached failure. Without an `errorBuilder`, the error is reported to `FlutterError.onError`.

saying these in an interview costs you the question

  • Image.network caches downloaded photos on disk between app launches.
  • Image.file works on the web because browsers expose a file system.
  • Only Image.network supports cacheWidth and cacheHeight.
  • Each Image widget decodes its own copy even when the URL is the same.
  • A failed network image stays cached, so retries never hit the server.