In Flutter, how do you bundle offline JSON question packs and sound files with an app and read them at runtime?
answer
- flutter: then assets: in pubspec.yaml
- trailing slash = one directory level
- the declared path is the key
- rootBundle.loadString returns a Future
- build error vs runtime error
basics
~20 sList the files or directories under flutter: assets: in pubspec.yaml, which copies them into the app's asset bundle, then read them by their declared path: rootBundle.loadString for text such as JSON, rootBundle.load for bytes such as sounds.
solid answer
~40 sAssets are declared in `pubspec.yaml` under `flutter:` then `assets:`, each entry a path relative to the pubspec. A file path adds that file; a path ending in `/` adds the files *directly* in that directory, so each subdirectory needs its own entry. At build time the `flutter` tool copies them into the asset bundle, and at runtime you read them by the same path used as a key: `rootBundle.loadString('assets/questions/history.json')` returns a `Future<String>`, and `rootBundle.load(...)` returns `Future<ByteData>` for binary files like sounds. A declared file that is missing fails the build with `No file or variants found`; loading an undeclared key fails at runtime with `Unable to load asset`.
code
yaml · 6 linesflutter:
uses-material-design: true
assets:
- assets/questions/
- assets/sounds/
- assets/sounds/effects/go deeper
Know the pubspec syntax, that a trailing slash adds one directory level, and that rootBundle.loadString and load read files by their declared path.
Explain the build step that copies files into the asset bundle, the difference between build-time and runtime errors, and why reads are asynchronous.
Organise assets so large packs load lazily and declarations stay in step with the folders, and use AssetManifest instead of hard-coded lists.
Decide which content ships in the binary as assets and which is downloaded later, weighing offline play against app size and update speed.
## What an asset is An **asset** is a file that ships inside the app and can be read at runtime: JSON data, configuration, images, audio, shaders. Unlike a file in the device's documents directory, it is read-only and identical on every install. A trivia game that must work offline is a typical case: its question packs are JSON files, and its "correct" and "wrong" sounds are audio files, all bundled with the build. ## Declaring assets in pubspec.yaml Assets are listed in the app's `pubspec.yaml`, under the `flutter:` section: ```yaml flutter: uses-material-design: true assets: - assets/questions/ - assets/sounds/ - assets/sounds/effects/ - assets/credits.txt ``` The rules the `flutter` tool applies: - Each entry is a path **relative to `pubspec.yaml`**. The folder name `assets` is a convention, not a requirement. - An entry ending in **`/`** includes every file **directly** in that directory. Subdirectories are **not** included, which is why `assets/sounds/effects/` needs its own line. (Resolution-aware image variants such as `2.0x/` folders are the one exception.) - An entry without a trailing slash names one file. - The order of entries does not matter. - **Indentation matters.** `assets:` must sit two spaces under `flutter:`; a mis-indented list is a common cause of the error `unable to find directory entry in pubspec.yaml`. An entry can also be a map with a `path:` key plus extra keys such as `transformers:` or `platforms:`, for build-time processing or per-platform bundling. ## How the files reach the app 1. On `flutter run` or `flutter build`, the tool reads the `assets:` list and copies each matching file into the **asset bundle**, a special archive packaged with the app. 2. If a declared file does not exist, the tool reports `No file or variants found for` that asset. 3. At runtime, code asks an `AssetBundle` for a **key**, which is the declared path, such as `assets/questions/history.json`. Keys match exactly, including case. ## Reading assets at runtime `package:flutter/services.dart` exposes **`rootBundle`**, the bundle built with the app: | Call | Returns | Typical use | |---|---|---| | `rootBundle.loadString(key)` | `Future<String>` (UTF-8) | JSON question packs, text | | `rootBundle.load(key)` | `Future<ByteData>` | sounds, binary data | | `rootBundle.loadStructuredData(key, parser)` | `Future<T>` | text parsed once into a model | Inside widgets, `DefaultAssetBundle.of(context)` is preferred, because it lets an ancestor substitute another bundle; it falls back to `rootBundle`. Images normally go through `Image.asset`, which resolves the bundle and resolution variants for you. Every call is asynchronous, so start the load once, in a repository or in `initState`, rather than in `build()`. Turning the JSON string into Dart objects is a separate step handled by `dart:convert` or generated models. ## Bundling only for some platforms Since Flutter 3.41 an entry can carry a `platforms:` list, so a large asset ships only where it is used. Valid values are `android`, `ios`, `web`, `linux`, `macos` and `windows`: ```yaml flutter: assets: - path: assets/sounds/desktop_ambience/ platforms: - macos - windows - linux ``` A phone build then leaves those files out entirely, and code that might run on every platform must not assume the asset exists everywhere. Platform code can read bundled assets too, through `AssetManager` on Android and `NSBundle` on iOS, which matters when a native plugin needs the same sound file. ## Two different failures | Symptom | Cause | |---|---| | Build prints `No file or variants found for ...` | a declared file is missing or misspelled on disk | | Build prints `unable to find directory entry in pubspec.yaml` | a declared directory is missing, often from bad indentation | | Runtime `Unable to load asset: "..."` | the key was never declared, or it differs from the declared path | ## Common mistakes - Expecting a directory entry to pull in its subfolders. - Loading `questions/history.json` when the declared path is `assets/questions/history.json`. - Using `dart:io` `File('assets/...')`: bundled assets are not ordinary files on the device, so read them through an `AssetBundle`. - Calling `rootBundle` in `main()` before `WidgetsFlutterBinding.ensureInitialized()`.
- Why can't the app open a bundled question pack with File('assets/questions/history.json')?Bundled assets live inside the app package, not as plain files at that path on the device, so `dart:io` cannot open them by their pubspec path. Read them through an `AssetBundle` (`rootBundle` or `DefaultAssetBundle.of(context)`), which knows how to fetch them on every platform, including the web.
- How can the app list every bundled question pack without hard-coding file names?Load the asset manifest with `AssetManifest.loadFromAssetBundle(rootBundle)` and call `listAssets()`, then filter keys that start with `assets/questions/`. Do not read `AssetManifest.json`: it was an undocumented detail, and current Flutter no longer generates it.
saying these in an interview costs you the question
- An entry like assets/sounds/ also includes every subdirectory under it.
- Any file in the project folder can be loaded without declaring it.
- The asset key is just the file name, without its folder path.
- Bundled assets can be opened with dart:io File using their pubspec path.
- The order of entries under assets: changes which file wins.