DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Android App Bundle Complete Guide — Play Feature Delivery/Dynamic Modules

What You'll Learn

This article explains Android App Bundle (AAB format, Play Feature Delivery, dynamic modules, conditional delivery, size optimization).


What is App Bundle

// build.gradle.kts (app)
android {
    bundle {
        language {
            enableSplit = true  // Split language resources
        }
        density {
            enableSplit = true  // Split screen density
        }
        abi {
            enableSplit = true  // Split CPU architecture
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Google Play optimizes APK per device. Average 20% size reduction.


Play Feature Delivery

// Dynamic feature module: build.gradle.kts
plugins {
    id("com.android.dynamic-feature")
}
android {
    namespace = "com.example.feature.camera"
}
dependencies {
    implementation(project(":app"))
}
Enter fullscreen mode Exit fullscreen mode
<!-- feature/camera/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:dist="http://schemas.android.com/apk/distribution">
    <dist:module
        dist:instant="false"
        dist:title="@string/title_camera">
        <dist:delivery>
            <dist:on-demand />
        </dist:delivery>
    </dist:module>
</manifest>
Enter fullscreen mode Exit fullscreen mode

Installing Dynamic Modules

class DynamicFeatureManager @Inject constructor(
    @ApplicationContext private val context: Context
) {
    private val splitInstallManager = SplitInstallManagerFactory.create(context)

    fun installModule(
        moduleName: String,
        onProgress: (Int) -> Unit,
        onSuccess: () -> Unit,
        onFailure: (Exception) -> Unit
    ) {
        val request = SplitInstallRequest.newBuilder()
            .addModule(moduleName)
            .build()

        splitInstallManager.startInstall(request)
            .addOnSuccessListener { sessionId ->
                splitInstallManager.registerListener { state ->
                    when (state.status()) {
                        SplitInstallSessionStatus.INSTALLED -> onSuccess()
                        SplitInstallSessionStatus.DOWNLOADING -> {
                            val progress = (state.bytesDownloaded() * 100 / state.totalBytesToDownload()).toInt()
                            onProgress(progress)
                        }
                        SplitInstallSessionStatus.FAILED -> {
                            onFailure(Exception("Install failed: ${state.errorCode()}"))
                        }
                        else -> {}
                    }
                }
            }
            .addOnFailureListener { onFailure(it) }
    }

    fun isModuleInstalled(moduleName: String): Boolean {
        return splitInstallManager.installedModules.contains(moduleName)
    }
}
Enter fullscreen mode Exit fullscreen mode

Compose UI

@Composable
fun DynamicFeatureScreen(
    featureManager: DynamicFeatureManager,
    moduleName: String,
    installedContent: @Composable () -> Unit
) {
    var installState by remember { mutableStateOf(
        if (featureManager.isModuleInstalled(moduleName)) InstallState.Installed
        else InstallState.NotInstalled
    )}
    var progress by remember { mutableIntStateOf(0) }

    when (installState) {
        InstallState.NotInstalled -> {
            Column(
                Modifier.fillMaxSize().padding(16.dp),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center
            ) {
                Text("This feature requires additional download")
                Spacer(Modifier.height(16.dp))
                Button(onClick = {
                    installState = InstallState.Installing
                    featureManager.installModule(
                        moduleName,
                        onProgress = { progress = it },
                        onSuccess = { installState = InstallState.Installed },
                        onFailure = { installState = InstallState.NotInstalled }
                    )
                }) { Text("Download") }
            }
        }
        InstallState.Installing -> {
            Column(
                Modifier.fillMaxSize(),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center
            ) {
                CircularProgressIndicator()
                Spacer(Modifier.height(8.dp))
                Text("Downloading: $progress%")
                LinearProgressIndicator(progress = { progress / 100f })
            }
        }
        InstallState.Installed -> installedContent()
    }
}

enum class InstallState { NotInstalled, Installing, Installed }
Enter fullscreen mode Exit fullscreen mode

Summary

Feature Implementation
APK splitting bundle { enableSplit = true }
On-demand delivery <dist:on-demand />
Install management SplitInstallManager
Progress tracking SplitInstallSessionStatus
  • Use AAB format for device-optimized APK delivery
  • Dynamic modules reduce initial download size
  • SplitInstallManager manages module installation
  • Conditional delivery supports country/API-level targeting

8 production-ready Android app templates (App Bundle optimized) are available.

Browse templatesGumroad

Related articles:

  • R8/ProGuard
  • Release checklist
  • Google Play publishing

Ready-Made Android App Templates

8 production-ready Android app templates with Jetpack Compose, MVVM, Hilt, and Material 3.

Browse templatesGumroad

Top comments (0)