Using Flutter Deferred Components to Split Code and Load Features On Demand
Learn how to defer Dart libraries in Flutter, build app bundles with `--deferred-components`, and load features at runtime while understanding limits and common pitfalls.
08 Jul 2026, 22:43 UTC

Why use deferred components in Flutter
If your app contains a large feature that is not needed at startup—such as an advanced editor, a game level, or a heavy analytics module—you can mark its Dart library as a deferred import. The Flutter toolchain then splits that library into a separate AOT snapshot (Android .so or iOS .framework) that is downloaded only when you call loadLibrary(). The result is a smaller initial download and the ability to load the feature on demand.
Worked example
Assume a library heavy_feature.dart that defines a function runDemo(). In the main Dart file:
import 'package:flutter/material.dart';
// Deferred import – the prefix “heavy” is used later to load the library
import 'package:my_app/heavy_feature.dart' deferred as heavy;
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: ElevatedButton(
onPressed: _loadAndRunHeavy,
child: const Text('Load heavy feature'),
),
),
),
);
}
Future _loadAndRunHeavy() async {
// 1️⃣ Load the deferred library – returns a Future that completes when the code is available
final lib = await heavy.loadLibrary();
// 2️⃣ Call a function from the loaded library
lib.runDemo();
}
}
The import line tells the compiler to place heavy_feature.dart in a separate deferred component. At runtime, heavy.loadLibrary() triggers the download of the corresponding snapshot, links it into the current isolate, and resolves the symbols.
Build configuration
To produce the split artifacts you must build a release bundle with the --deferred-components flag.
- Android (App Bundle):
flutter build appbundle --deferred-components - iOS (IPA):
flutter build ipa --deferred-components
Run these commands from the root of your Flutter project. No special permissions are required beyond the usual Flutter tooling access.
How the mechanism works
The Flutter compiler generates a separate kernel file for each deferred library. During the build step the toolchain packages each kernel into an AOT snapshot:
- Android: a
.sofile placed in thedeferred-componentsdirectory of the generated App Bundle. - iOS: a
.frameworkbundle placed in the same directory of the IPA.
When loadLibrary() is invoked, the Flutter engine contacts the Play Store (or App Store) or a custom CDN you have configured, downloads the snapshot, and links it into the running isolate. The code then executes in the same isolate as the rest of the app; there is no automatic separate isolate.
Limits and practical considerations
- State sharing: Because the deferred code runs in the same isolate, you can call its functions directly, but any mutable state must be communicated via messages (e.g., using
Isolatechannels,StreamController, or a state‑management solution) if you need to keep data across loads. - Build complexity: Adding deferred imports increases the number of build artifacts and requires the
--deferred-componentsflag for release builds. Debug builds simulate deferred loading via the VM service, so you must test on a real device or internal test track to see the actual download behavior. - Store delivery: On Android you must configure Play Feature Delivery (or use the internal app sharing track) so that the Play Store can serve the deferred component. On iOS you must enable On‑Demand Resources in Xcode and set the appropriate asset tags; otherwise the engine will fail to download the snapshot.
- Assets: Deferred components only split Dart code. Images, fonts, or other assets bundled with the library are not automatically split; you need to move them to separate asset bundles or load them with
AssetBundleafter the library is loaded if you want to reduce the initial asset size. - Web support: The
deferredkeyword is ignored on Flutter Web; code splitting there relies on dynamicimport()statements instead.
Common mistakes to avoid
- Calling
heavy.runDemo()before awaitingloadLibrary()– this throws aNoSuchMethodErrorbecause the library hasn’t been linked yet. - Assuming the deferred code runs in a separate isolate – it does not; if you need true isolation you must spawn an
Isolateyourself. - Omitting the
--deferred-componentsflag when building release bundles – the resulting bundle will contain all code in the main APK/IPA, defeating the purpose. - Testing only on a debugger or simulator – deferred loading is a no‑op in debug mode; you must install a release bundle via Play Store internal testing or TestFlight to observe the on‑demand download.
- Neglecting to configure Play Feature Delivery or On‑Demand Resources – the engine will report a failure to locate the deferred component when
loadLibrary()resolves.
How to verify the split
After running flutter build appbundle --deferred-components, navigate to build/app/outputs/bundle/release/app.aab (or the equivalent IPA directory) and unpack it. You should see a folder named deferred-components containing one or more .so files (Android) or .framework bundles (iOS). Their presence confirms that the compiler created separate snapshots for each deferred import.
On a device, install the bundle through the internal test track, start the app, and press the button that triggers loadLibrary(). Using Android Studio’s Profiler or Xcode’s console you can observe a network request for the deferred component followed by the execution of runDemo(). If the library fails to load, check the log for messages such as "Failed to load deferred component" which usually indicates a missing store configuration.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.