Integrate Android with Storyblok
Use Storyblok to manage the content of your Android project.
This guide has been tested with the following versions:
- Android Studio
Quail 2 | 2026.1.2 - Storyblok Compose
0.3.0 - Storyblok Material 3
0.3.0
Set up the project and create a Storyblok space.
-
Create a new space
Log in to Storyblok, select + Add Space → New Space, and follow the steps.
-
Create an Android project
Create a new Android project in Android Studio following the official Create a project guide. Select the Empty Activity template to get a minimal Jetpack Compose setup, and set the Minimum SDK to API 30 or higher.
Add dependencies
Section titled “Add dependencies”In the version catalog TOML file, add the Storyblok libraries and the Kotlin Serialization plugin.
[versions]agp = "9.2.1"…composeBom = "2026.02.01"storyblok = "0.3.0"
[libraries]androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }…androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }storyblok-compose-android = { group = "com.storyblok", name = "storyblok-compose-android", version.ref = "storyblok" }storyblok-material3-android = { group = "com.storyblok", name = "storyblok-material3-android", version.ref = "storyblok" }
[plugins]android-application = { id = "com.android.application", version.ref = "agp" }kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }Update app/build.gradle.kts to apply the serialization plugin, set Kotlin compiler options, and add the dependencies.
plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.serialization)}
android { …}
kotlin { compilerOptions { optIn.add("kotlin.uuid.ExperimentalUuidApi") }}
dependencies { implementation(platform(libs.androidx.compose.bom)) … implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.storyblok.compose.android) implementation(libs.storyblok.material3.android)}Finally, add the internet permission to the AndroidManifest.xml.
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.INTERNET" /> …Now select Sync Project with Gradle Files to download the added dependencies and re-analyze the project.
Fetch a single story
Section titled “Fetch a single story”Replace the following in MainActivity.kt with an invocation of the Storyblok composable function. This is the entry point for adding Storyblok content to your composition.
import com.example.myapplication.ui.theme.MyApplicationThemeimport androidx.compose.runtime.getValueimport androidx.lifecycle.compose.collectAsStateWithLifecycleimport com.storyblok.compose.Storyblokimport com.storyblok.compose.provider.blockProviderimport com.storyblok.ktor.Api.Config.Region.*import com.storyblok.ktor.Api.Config.Version.*
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { MyApplicationTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Greeting( name = "Android", modifier = Modifier.padding(innerPadding) ) Storyblok( accessToken = "YOUR_ACCESS_TOKEN", version = Draft, region = EU, // Choose the correct region from your Space. blockProvider = blockProvider( fallback = { it, modifier -> Text("Unknown block type '${it.component}'", modifier) } ) { /* blocks registered here in the next step */ } ) { val story by story("home").collectAsStateWithLifecycle(null) story?.content?.let { Block(it, Modifier.padding(innerPadding)) } } } } } }}The Storyblok composable function provides functions for story fetching and dynamic block rendering inside its trailing lambda scope:
- First, the
story()function is used to fetch the preconfiguredhomestory from the space. The function returns aFlowthat emits first from cache (if present), then from the network (once connected). - Next, the
Block()composable function is invoked with the story content. This function uses the composable function registered for the story’s content type in theblockProvider.
You can now build and run the app. However, since no block types have been registered yet, only the content of the fallback composable supplied via the blockProvider class is rendered:
Unknown block type ‘page’
Create and register blocks
Section titled “Create and register blocks”First, create Component subclasses for each of the preconfigured blocks defined in the block library of the space.
Create a new BlockLibrary.kt file:
package com.example.myapplication
import kotlinx.serialization.SerialNameimport kotlinx.serialization.Serializableimport com.storyblok.cdn.schema.Component
@Serializable@SerialName("feature")data class Feature(val name: String): Component()
@Serializable@SerialName("grid")data class Grid(val columns: List<Component>): Component()
@Serializable@SerialName("page")data class Page(val body: List<Component>): Component()
@Serializable@SerialName("teaser")data class Teaser(val headline: String): Component()The classes are marked with @Serializable and @SerialName annotations from the Kotlin Serialization plugin so they can be deserialized from the Content Delivery API.
Next, register composables for each of these blocks inside the blockProvider in MainActivity.kt.
import com.storyblok.ktor.Api.Config.Version.*import androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.fillMaxWidthimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.layout.FlowRowimport androidx.compose.foundation.lazy.LazyColumnimport androidx.compose.foundation.lazy.itemsimport androidx.compose.material3.MaterialTheme.typographyimport androidx.compose.ui.unit.dp… blockProvider = blockProvider( fallback = { it, _ -> Text("Unknown block type '${it.component}'") } ) { /* blocks registered here in the next step */ block<Page> { page, modifier -> LazyColumn(modifier) { items(page.body, key = { it.uid }) { Block(it, Modifier.fillMaxWidth()) } } } block<Feature> { feature, modifier -> Text(feature.name, modifier.padding(16.dp)) } block<Teaser> { teaser, modifier -> Text(teaser.headline, modifier.padding(16.dp), style = typography.headlineMedium) } block<Grid> { grid, modifier -> FlowRow(modifier) { grid.columns.forEach { Block(it) } } } }…The blockProvider function provides functions for component registration and nested component rendering inside its trailing lambda scope:
- First, the
block()function is used to register a composable function for each of the fourComponentsubclasses:Page,Feature,Teaser, andGrid. - Next, the
Block()composable function is invoked to render the nested blocks insidePageandGrid.
Run the app again and see the complete home story with the teaser, feature, and grid components being rendered.
Related resources
Section titled “Related resources”Was this page helpful?
This site uses reCAPTCHA and Google's Privacy Policy (opens in a new window).Terms of Service (opens in a new window) apply.
Get in touch with the Storyblok community