In Flutter, how do you declare and load assets that live in a package, such as a shared trivia-content package?
answer
- declared in the package's own pubspec
- key prefix packages/<name>/
- package: parameter on AssetImage
- lib/ is implied, not written
- the package's own code uses the prefix too
basics
~20 sA package declares its assets in its own pubspec.yaml, and they are bundled into every app that depends on it under the key packages/<package>/<path>. Load them with that full key, or pass package: to AssetImage or Image.asset.
solid answer
~30 sAssets declared under `flutter: assets:` in a package's `pubspec.yaml` are bundled automatically into any app that depends on it. Their key gains the prefix `packages/<package_name>/`, so the pack declared as `assets/questions/history.json` in `trivia_content` is read with `rootBundle.loadString('packages/trivia_content/assets/questions/history.json')`. For images, `AssetImage('badges/star.png', package: 'trivia_content')` or `Image.asset(..., package: ...)` adds the prefix for you. The package's *own* code must use the prefixed key too, because at runtime there is one bundle, the app's. A package may also keep undeclared files under `lib/`; an app can opt into one by declaring `packages/<name>/<path>` with `lib/` left out.
code
dart · 14 linesimport 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
const _package = 'trivia_content';
class TriviaContent {
static Future<String> loadPackJson(AssetBundle bundle, String category) {
return bundle.loadString('packages/$_package/assets/questions/$category.json');
}
static Widget badge(String category) {
return Image.asset('assets/badges/$category.png', package: _package);
}
}go deeper
Know that package assets are loaded with a packages/<name>/ prefix or the package: argument on AssetImage and Image.asset.
Explain why the prefix exists, that package code needs it too, and how an app opts into a package's undeclared lib/ files.
Design a content package with an API that hides key construction, accepts an AssetBundle, and keeps optional large files opt-in.
Decide how content is split across packages shared by several apps, and who owns the versioning of that content.
## Why package assets need a prefix A Flutter app has **one** asset bundle at runtime. When the app depends on packages that bring their own assets, all of them land in that single bundle. To keep two packages' `assets/questions/history.json` files from colliding, the `flutter` tool files every package asset under a **key prefixed with `packages/<package_name>/`**. Take a trivia game that keeps its content in a separate package, `trivia_content`, shared by the phone app and a tablet app: ```text trivia_content/ pubspec.yaml assets/questions/history.json assets/sounds/buzz.mp3 lib/trivia_content.dart ``` ## Declaring assets in the package The package lists its assets in **its own** `pubspec.yaml`, exactly as an app would: ```yaml name: trivia_content flutter: assets: - assets/questions/ - assets/sounds/ ``` Any app that depends on `trivia_content` gets these files bundled automatically; the app does **not** repeat the declarations. ## Loading them | From | How | |---|---| | `rootBundle` or `DefaultAssetBundle.of(context)` | `loadString('packages/trivia_content/assets/questions/history.json')` | | `AssetImage` | `AssetImage('assets/badges/star.png', package: 'trivia_content')` | | `Image.asset` | `Image.asset('assets/badges/star.png', package: 'trivia_content')` | `AssetImage` builds its key as `packages/$package/$assetName`, so the two forms are equivalent. `AssetBundle` methods have no `package` parameter; you write the prefix yourself. **The package's own code follows the same rule.** A widget inside `trivia_content` that shows its own badge must pass `package: 'trivia_content'`, or use the full prefixed key, because it runs inside the app's bundle, where the unprefixed key would look for an app asset. ## Files under lib/ that the package does not declare A package can ship files in its `lib/` folder without declaring them, leaving the choice to each app. An app bundles one by declaring it with the package prefix and **without** `lib/`: ```yaml flutter: assets: - packages/trivia_content/sounds/fanfare.mp3 ``` That entry refers to `trivia_content/lib/sounds/fanfare.mp3`; the `lib/` segment is implied. This pattern suits optional content, such as a large sound set only some apps want. ## Resolution variants and packages Resolution-aware variants work the same way inside a package: `assets/badges/2.0x/star.png` next to `assets/badges/star.png` is picked up by `AssetImage(..., package: 'trivia_content')`. ## How a package asset key is formed 1. The package declares `assets/questions/history.json` in its own `pubspec.yaml`. 2. When the app builds, the tool walks the app's dependencies and bundles each package's declared assets under `packages/<package_name>/` plus the declared path. 3. The asset manifest records the prefixed key, so `AssetManifest.listAssets()` in the app shows `packages/trivia_content/assets/questions/history.json`. 4. At runtime, `AssetImage` with `package:` or a hand-written prefixed key reaches that entry through the app's bundle. Because the key includes the package name, **renaming the package changes every key**. Code that builds keys in one place, inside the package, survives a rename; string literals scattered across apps do not. ## Errors and how to read them 1. `Unable to load asset: "assets/questions/history.json"` from a package's code usually means the prefix is missing. 2. The build prints `Could not resolve package for asset` when an app declares a `packages/<name>/...` path for a package it does not depend on. 3. `Could not locate package:<name>. Try running flutter pub get.` means the dependency graph is stale. ## Good practice for content packages - Expose a small Dart API from the package, such as `TriviaContent.loadPack(bundle, category)`, that builds the prefixed key internally, so apps never hard-code paths. - Take an `AssetBundle` parameter in that API, so apps and tests can pass their own bundle. - Keep large optional files undeclared in `lib/` only when apps really differ in what they need; otherwise declare them in the package. - Fonts from packages have their own declaration rules and a `package` argument on the text style; they are configured separately from these data assets.
- Why does a widget inside the package fail to load its own badge with Image.asset('assets/badges/star.png')?At runtime the package's code reads from the app's single bundle, where the package's files live under `packages/trivia_content/`. Without `package: 'trivia_content'`, the key is looked up as an app asset and is not found. Pass the `package` argument or use the full prefixed key.
- Can two packages both ship assets/questions/history.json without clashing?Yes. Each package's assets are keyed under its own `packages/<package_name>/` prefix, so the two files become `packages/pack_a/assets/questions/history.json` and `packages/pack_b/assets/questions/history.json`. An app's own assets cannot clash with them either, because app keys carry no prefix.
saying these in an interview costs you the question
- An app must repeat a package's asset declarations in its own pubspec.yaml.
- Package code can load its own assets without the packages/ prefix.
- Declaring an undeclared lib/ asset requires writing lib/ in the path.
- rootBundle.loadString takes a package: argument like AssetImage does.
- Package asset keys use the package: URI scheme.