Mobile Development 15 min read

Implementing Flutter Deferred Components without Google Play: Official Scheme and Custom Demo

This article explains the official Flutter Deferred Components implementation, explores its limitations in China, and provides a step‑by‑step custom solution with code examples that enables dynamic Dart code delivery without relying on Google Play, complete with local verification instructions.

JD Tech
JD Tech
JD Tech
Implementing Flutter Deferred Components without Google Play: Official Scheme and Custom Demo

Deferred Components is a feature introduced in Flutter 2.2 that relies on Dart 2.13 split AOT compilation, allowing Dart libraries or asset bundles to be downloaded and installed at runtime as separate .so files, reducing app size and enabling dynamic delivery.

The article first introduces the official Google Play‑based implementation, explains its architecture, and lists the basic steps: adding the Play Core dependency, modifying Application onCreate and attachBaseContext, configuring pubspec.yaml , creating deferred Dart files ( box.dart , some_widgets.dart ), adding navigation, building the app bundle, and performing local verification with bundletool.

Because the official solution depends on Google Play, the author proposes a custom implementation that replaces PlayStoreDeferredComponentManager . The custom manager simulates network download by moving a pre‑built .so file from external storage to the app’s private directory, adds a 2‑second delay, and reports success or failure via DeferredComponentChannel .

Key methods such as installDeferredComponent and loadDartLibrary are re‑implemented to locate the appropriate .so file, construct search paths, and invoke flutterJNI.loadDartDeferredLibrary . The Application class is updated to use the custom manager, and the Play Core dependency is removed.

Finally, the article describes how to verify the demo locally: building the appbundle, extracting the split APK, locating the .so file, installing with bundletool, and pushing the .so file to the device’s storage to trigger successful loading.

The author provides links to the minimal demo code and references to official documentation.

dependencies {
  implementation "com.google.android.play:core:1.8.0"
}
@Override
protected void onCreate() {
  super.onCreate();
  // responsible for deferred components download and install
  PlayStoreDeferredComponentManager deferredComponentManager = new PlayStoreDeferredComponentManager(this, null);
  FlutterInjector.setInstance(new FlutterInjector.Builder()
    .setDeferredComponentManager(deferredComponentManager).build());
}

@Override
protected void attachBaseContext(Context base) {
  super.attachBaseContext(base);
  // Emulates installation of future on‑demand modules using SplitCompat.
  SplitCompat.install(this);
}
// box.dart
import 'package:flutter/widgets.dart';
/// A simple blue 30x30 box.
class DeferredBox extends StatelessWidget {
  DeferredBox();
  @override
  Widget build(BuildContext context) {
    return Container(
      height: 30,
      width: 30,
      color: Colors.blue,
    );
  }
}
import 'box.dart' deferred as box;
class SomeWidget extends StatefulWidget {
  @override
  _SomeWidgetState createState() => _SomeWidgetState();
}
class _SomeWidgetState extends State
{
  Future
_libraryFuture;
  @override
  void initState() {
    // only after calling loadLibrary will the deferred component be downloaded
    _libraryFuture = box.loadLibrary();
    super.initState();
  }
  @override
  Widget build(BuildContext context) {
    return FutureBuilder
(
      future: _libraryFuture,
      builder: (BuildContext context, AsyncSnapshot
snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          if (snapshot.hasError) {
            return Text('Error: ${snapshot.error}');
          }
          return box.DeferredBox();
        }
        return CircularProgressIndicator();
      },
    );
  }
}
java -jar bundletool.jar build-apks --bundle=
/build/app/outputs/bundle/release/app-release.aab --output=
/app.apks --local-testing
java -jar bundletool.jar install-apks --apks=
/app.apks
@Override
public void installDeferredComponent(int loadingUnitId, String componentName) {
    String resolvedComponentName = componentName != null ? componentName : loadingUnitIdToComponentNames.get(loadingUnitId);
    if (resolvedComponentName == null) {
        Log.e(TAG, "Deferred component name was null and could not be resolved from loading unit id.");
        return;
    }
    if (resolvedComponentName.equals("") && loadingUnitId > 0) {
        loadDartLibrary(loadingUnitId, resolvedComponentName);
        return;
    }
    // Simulate network download by moving .so from external storage
    new Thread(() -> {
        boolean result = moveSoToPrivateDir();
        if (result) {
            new Handler(Looper.getMainLooper()).postDelayed(() -> {
                loadAssets(loadingUnitId, resolvedComponentName);
                loadDartLibrary(loadingUnitId, resolvedComponentName);
                if (channel != null) {
                    channel.completeInstallSuccess(resolvedComponentName);
                }
            }, 2000);
        } else {
            new Handler(Looper.getMainLooper()).post(() -> {
                Toast.makeText(context, "未在sd卡中找到so文件", Toast.LENGTH_LONG).show();
                if (channel != null) {
                    channel.completeInstallError(resolvedComponentName, "未在sd卡中找到so文件");
                }
                if (flutterJNI != null) {
                    flutterJNI.deferredComponentInstallFailure(loadingUnitId, "未在sd卡中找到so文件", true);
                }
            });
        }
    }).start();
}
@Override
public void loadDartLibrary(int loadingUnitId, String componentName) {
    if (!verifyJNI()) {
        return;
    }
    if (loadingUnitId < 0) {
        return;
    }
    String aotSharedLibraryName = loadingUnitIdToSharedLibraryNames.get(loadingUnitId);
    if (aotSharedLibraryName == null) {
        aotSharedLibraryName = flutterApplicationInfo.aotSharedLibraryName + "-" + loadingUnitId + ".part.so";
    }
    String abi;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        abi = Build.SUPPORTED_ABIS[0];
    } else {
        abi = Build.CPU_ABI;
    }
    String pathAbi = abi.replace("-", "_");
    List
apkPaths = new ArrayList<>();
    List
soPaths = new ArrayList<>();
    Queue
searchFiles = new LinkedList<>();
    searchFiles.add(context.getFilesDir());
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        for (String path : context.getApplicationInfo().splitSourceDirs) {
            searchFiles.add(new File(path));
        }
    }
    while (!searchFiles.isEmpty()) {
        File file = searchFiles.remove();
        if (file != null && file.isDirectory() && file.listFiles() != null) {
            for (File f : file.listFiles()) {
                searchFiles.add(f);
            }
            continue;
        }
        String name = file.getName();
        if (name.endsWith(".apk") && (name.startsWith(componentName) || name.startsWith("split_config")) && name.contains(pathAbi)) {
            apkPaths.add(file.getAbsolutePath());
            continue;
        }
        if (name.equals(aotSharedLibraryName)) {
            soPaths.add(file.getAbsolutePath());
        }
    }
    List
searchPaths = new ArrayList<>();
    searchPaths.add(aotSharedLibraryName);
    for (String path : apkPaths) {
        searchPaths.add(path + "!lib/" + abi + "/" + aotSharedLibraryName);
    }
    for (String path : soPaths) {
        searchPaths.add(path);
    }
    flutterJNI.loadDartDeferredLibrary(loadingUnitId, searchPaths.toArray(new String[searchPaths.size()]));
}
override fun onCreate() {
    super.onCreate()
    val deferredComponentManager = CustomDeferredComponentsManager(this, null)
    val injector = FlutterInjector.Builder().setDeferredComponentManager(deferredComponentManager).build()
    FlutterInjector.setInstance(injector)
}
Fluttermobile developmentAndroidDeferred ComponentsDynamic Delivery
JD Tech
Written by

JD Tech

Official JD technology sharing platform. All the cutting‑edge JD tech, innovative insights, and open‑source solutions you’re looking for, all in one place.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.