Instant & Dynamic Features

Everything so far assumes a user installs the whole app up front. Play Feature Delivery offers two more specialized options built on the same App Bundle format from earlier lessons. Both are genuinely advanced and fairly niche compared to everything else in this course — most apps, including RecipeBox as built throughout this tutorial, never need either one. They're covered here for awareness of what exists once an app's needs grow large enough to justify the extra complexity.

Google Play Instant: Try Before You Install

A Play Instant app (or "instant experience") lets a user run a lightweight version of an app directly from a Play Store listing, a web link, or a "Try Now" button — without installing anything first. If RecipeBox offered this, tapping a shared recipe link could open a live, limited RecipeBox experience showing just that one recipe, with a normal "Install" prompt available if the user wants the full app afterward.

User taps link

No RecipeBox installed

→

Instant experience streams

Small, capped-size module runs immediately

→

Optional install

User can install the full app to keep using it

Instant apps have a strict size cap per module (historically around 15 MB) and cannot use every Android API — features requiring deep OS integration or large native libraries typically don't fit the model. This mostly suits highly focused single-purpose flows, not a full-featured app like RecipeBox.

Dynamic Feature Modules: On-Demand Delivery

A Dynamic Feature Module is a separate Gradle module bundled into the same App Bundle, but not downloaded at install time — it's fetched later, only if and when a user actually needs it. Imagine RecipeBox adding an optional "Meal Planner" module (drag recipes onto a weekly calendar, generate a shopping list) that most users would never touch: shipping it as a dynamic feature keeps the base install small for everyone, and only meal-planning users ever download that extra code.

// settings.gradle
include ":app", ":mealplanner"
// mealplanner/build.gradle (Groovy DSL)
plugins {
    id 'com.android.dynamic-feature'
}

android {
    namespace "com.example.recipebox.mealplanner"
    compileSdk 34
}

dependencies {
    implementation project(":app")
}
// app/build.gradle (Groovy DSL) — base module declares the feature
android {
    dynamicFeatures = [":mealplanner"]
}
Explanation:
  • The com.android.dynamic-feature plugin marks :mealplanner as a module Play can deliver separately, rather than a normal library module always bundled in.
  • dynamicFeatures on the :app module lists every such feature so the bundling process knows they exist.
  • The feature module depends on :app (for shared classes like Recipe), not the other way around — the base app must work fine with the feature never installed at all.

SplitInstallManager Basics

At runtime, requesting a dynamic feature's download goes through SplitInstallManager from the Play Core library, which reports progress and completion the same way the earlier Firebase Storage lesson's UploadTask listeners did.

// Inside an Activity, when the user taps "Open Meal Planner"
SplitInstallManager splitInstallManager = SplitInstallManagerFactory.create(this);

SplitInstallRequest request = SplitInstallRequest.newBuilder()
        .addModule("mealplanner")
        .build();

splitInstallManager.startInstall(request)
    .addOnSuccessListener(sessionId -> {
        Log.d("RecipeBox", "Meal Planner module installed");
        openMealPlannerScreen();
    })
    .addOnFailureListener(e -> {
        Log.e("RecipeBox", "Meal Planner module failed to install", e);
    });
Explanation: Once startInstall() succeeds, the module's classes and resources become available in the running process just like anything shipped in the base app — no separate app, no separate process, just code that wasn't present a moment earlier.

When (Not) to Reach for This

Use dynamic features when: a genuinely large, optional piece of functionality is used by a small minority of users, and the base app's install size matters enough to justify the added build complexity.

Skip them when: the app is small-to-medium sized already (RecipeBox, as built in this course, would not benefit), or when App Size Optimization techniques already keep the base install reasonably lean.

This closes out Build & Publish. The next section, Best Practices, starts with Clean Architecture (Java) — organizing RecipeBox's code into layers that stay maintainable as the app grows.