---
title: Integrate Android with Storyblok
description: This guide walks you through integrating Storyblok as a headless CMS, fetching content, and building Compose components to render it effectively in your Android project.
url: https://www.storyblok.com/docs/guides/android
---

# 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`

## Setup

Set up the project and create a Storyblok space.

[No Storyblok account?](https://app.storyblok.com/#/signup)Sign up and create a space for free

1.  **Create a [new space](https://app.storyblok.com/#/me/spaces/new?tab=select-plan)**
    
    Log in to [Storyblok](https://app.storyblok.com/#/me/spaces), select **\+ Add Space** → **New Space**, and follow the steps.
    
2.  **Create an Android project**
    
    Create a new Android project in Android Studio following the official [Create a project guide](https://developer.android.com/studio/projects/create-project). Select the **Empty Activity** template to get a minimal Jetpack Compose setup, and set the **Minimum SDK** to API 30 or higher.
    

## Add dependencies

In the version catalog TOML file, add the Storyblok libraries and the Kotlin Serialization plugin.

gradle/libs.versions.toml

```kotlin
[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.

app/build.gradle.kts

```kotlin
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`.

app/src/main/AndroidManifest.xml

```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

Replace the following in `MainActivity.kt` with an invocation of the [`Storyblok`](https://storyblok.github.io/storyblok-kotlin/storyblok-compose/com.storyblok.compose/-storyblok.html) composable function. This is the entry point for adding Storyblok content to your composition.

> [!TIP]
> Learn how to get an [access token](https://www.storyblok.com/docs/concepts/access-tokens#content-delivery-api-access-tokens) for your Storyblok project.

app/src/main/java/com/example/myapplication/MainActivity.kt

```kotlin
import com.example.myapplication.ui.theme.MyApplicationTheme
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.storyblok.compose.Storyblok
import com.storyblok.compose.provider.blockProvider
import 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)) }
          }
        }
      }
    }
  }
}
```

> [!WARNING]
> Ensure to set the correct [`region`](https://storyblok.github.io/storyblok-kotlin/ktor-client-storyblok/com.storyblok.ktor/-api/-config/-region/index.html) value depending on the server location of your Storyblok space.

The [`Storyblok`](https://storyblok.github.io/storyblok-kotlin/storyblok-compose/com.storyblok.compose/-storyblok.html) composable function provides functions for story fetching and dynamic block rendering inside its trailing lambda [scope](https://storyblok.github.io/storyblok-kotlin/storyblok-compose/com.storyblok.compose/-storyblok-scope/index.html):

-   First, the [`story()`](https://storyblok.github.io/storyblok-kotlin/storyblok-compose/com.storyblok.compose/-storyblok-scope/index.html#816830072%2FFunctions%2F-825163495) function is used to fetch the preconfigured `home` story from the space. The function returns a `Flow` that emits first from cache (if present), then from the network (once connected).
-   Next, the [`Block()`](https://storyblok.github.io/storyblok-kotlin/storyblok-compose/com.storyblok.compose/-block-scope/-block.html) composable function is invoked with the story content. This function uses the composable function registered for the story’s content type in the [`blockProvider`](https://storyblok.github.io/storyblok-kotlin/storyblok-material3/com.storyblok.compose.provider/block-provider.html).

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

First, create [`Component`](https://storyblok.github.io/storyblok-kotlin/content-api-client/com.storyblok.cdn.schema/-component/index.html) subclasses for each of the preconfigured blocks defined in the block library of the space.

Create a new `BlockLibrary.kt` file:

app/src/main/java/com/example/myapplication/BlockLibrary.kt

```kotlin
package com.example.myapplication

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import 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](https://www.storyblok.com/docs/api/content-delivery/v2).

> [!WARNING]
> The serial name must match the technical name given to the block in the block library.

Next, register composables for each of these blocks inside the [`blockProvider`](https://storyblok.github.io/storyblok-kotlin/storyblok-material3/com.storyblok.compose.provider/block-provider.html) in `MainActivity.kt`.

app/src/main/java/com/example/myapplication/MainActivity.kt

```kotlin
import com.storyblok.ktor.Api.Config.Version.*
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme.typography
import 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`](https://storyblok.github.io/storyblok-kotlin/storyblok-material3/com.storyblok.compose.provider/block-provider.html) function provides functions for component registration and nested component rendering inside its trailing lambda [scope](https://storyblok.github.io/storyblok-kotlin/storyblok-compose/com.storyblok.compose.provider/-block-provider-scope/index.html):

-   First, the [`block()`](https://storyblok.github.io/storyblok-kotlin/storyblok-compose/com.storyblok.compose.provider/-block-provider-scope/block.html) function is used to register a composable function for each of the four [`Component`](https://storyblok.github.io/storyblok-kotlin/content-api-client/com.storyblok.cdn.schema/-component/index.html) subclasses: `Page`, `Feature`, `Teaser`, and `Grid`.
-   Next, the [`Block()`](https://storyblok.github.io/storyblok-kotlin/storyblok-compose/com.storyblok.compose/-block-scope/-block.html) composable function is invoked to render the nested blocks inside `Page` and `Grid`.

Run the app again and see the complete home story with the teaser, feature, and grid components being rendered.

## Related resources

[Storyblok Kotlin Compose SDK Reference](https://storyblok.github.io/storyblok-kotlin/storyblok-compose/index.html)

[Concept: Blocks](/docs/concepts/blocks)

[Content Delivery API: Retrieve a Single Story](/docs/api/content-delivery/v2/stories/retrieve-a-single-story)

[Android Developers: Jetpack Compose Documentation](https://developer.android.com/develop/ui/compose/documentation)

[Consuming Flows Safely in Jetpack Compose](https://medium.com/androiddevelopers/consuming-flows-safely-in-jetpack-compose-cde014d0d5a3)

## Pagination

-   [Next: Visual Preview in Android](/docs/guides/android/visual-preview)
