Skip to content

Visual Preview in Android

Enhance your editorial and development experience by previewing draft content in the debug build of your Android app.

First, add the following to app/build.gradle.kts to enable BuildConfig.

app/build.gradle.kts
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.kotlin.serialization)
}
android {
buildFeatures {
compose = true
buildConfig = true
}
}

Then replace the following in MainActivity.kt to set version based on the build type.

app/src/main/java/com/example/myapplication/MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MyApplicationTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Storyblok(
accessToken = "YOUR_ACCESS_TOKEN",
version = Draft,
version = if (BuildConfig.DEBUG) Draft else Published,
region = EU, // Choose the correct region from your Space.
) {
val story by story("home").collectAsStateWithLifecycle(null)
story?.content?.let { Block(it, Modifier.padding(innerPadding)) }
}
}
}
}
}
}

With these changes, debug builds of the Android app now fetch draft content from Storyblok, while release builds fetch published content.

With version set to Draft, any changes you save in Storyblok appear the next time the story() function is invoked.

Replace the following in MainActivity.kt with a pull-to-refresh implementation so the story can be refreshed without restarting the app.

app/src/main/java/com/example/myapplication/MainActivity.kt
import androidx.compose.ui.unit.dp
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.onCompletion
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MyApplicationTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Storyblok(
accessToken = "YOUR_ACCESS_TOKEN",
version = if (BuildConfig.DEBUG) Draft else Published,
region = EU, // Choose the correct region from your Space.
) {
val story by story("home").collectAsStateWithLifecycle(null)
story?.content?.let { Block(it, Modifier.padding(innerPadding)) }
var isRefreshing: Boolean by remember { mutableStateOf(true) }
val story by
remember {
snapshotFlow { isRefreshing }
.filter { it }
.flatMapLatest {
story("home").onCompletion { isRefreshing = false }
}
}
.collectAsStateWithLifecycle(null)
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = { isRefreshing = true },
modifier = Modifier.fillMaxSize()
) {
story?.content?.let { Block(it, Modifier.padding(innerPadding)) }
}
}
}
}
}
}
}

The story() function is now invoked as follows:

  • The new isRefreshing state starts as true to trigger the initial load automatically.
  • snapshotFlow { isRefreshing } converts the state to a Flow, and .filter { it } ensures the story is only fetched when isRefreshing becomes true.
  • .flatMapLatest { ... } cancels any in-flight story request and invokes story() to start a new one, setting isRefreshing back to false when the flow completes.
  • PullToRefreshBox wraps the story content and triggers a refresh when the user pulls down.

With this in place, make a change to the home story, save it, then pull down in the debug build to immediately see the updated draft content.

Was this page helpful?

What went wrong?

This site uses reCAPTCHA and Google's Privacy Policy (opens in a new window).Terms of Service (opens in a new window) apply.