Visual Preview in Android
Enhance your editorial and development experience by previewing draft content in the debug build of your Android app.
Set version based on build type
Section titled “Set version based on build type”First, add the following to app/build.gradle.kts to enable BuildConfig.
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.
…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.
Refresh draft content
Section titled “Refresh draft 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.
…import androidx.compose.ui.unit.dpimport androidx.compose.material3.pulltorefresh.PullToRefreshBoximport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.setValueimport androidx.compose.runtime.snapshotFlowimport kotlinx.coroutines.flow.filterimport kotlinx.coroutines.flow.flatMapLatestimport 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
isRefreshingstate starts astrueto trigger the initial load automatically. snapshotFlow { isRefreshing }converts the state to aFlow, and.filter { it }ensures the story is only fetched whenisRefreshingbecomestrue..flatMapLatest { ... }cancels any in-flight story request and invokesstory()to start a new one, settingisRefreshingback tofalsewhen the flow completes.PullToRefreshBoxwraps 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.
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