Skip to content

Dynamic Navigation in Android

Use Navigation 3 to set up a catch-all navigation strategy in your Android project and render new stories dynamically.

In the version catalog TOML file, add the Navigation 3 libraries.

gradle/libs.versions.toml
[versions]
agp = "9.2.1"
storyblok = "0.3.0"
nav3Core = "1.0.1"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
storyblok-material3-android = { group = "com.storyblok", name = "storyblok-material3-android", version.ref = "storyblok" }
androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "nav3Core" }
androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "nav3Core" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }

Update app/build.gradle.kts to add the dependencies.

app/build.gradle.kts
dependencies {
implementation(platform(libs.androidx.compose.bom))
implementation(libs.storyblok.material3.android)
implementation(libs.androidx.navigation3.ui)
implementation(libs.androidx.navigation3.runtime)
}

Now select Sync Project with Gradle Files to download the added dependencies and re-analyze the project.

Create a StoryKey.kt file to define a navigation key that can represent any story, identified by either its UUID or slug.

app/src/main/java/com/example/myapplication/StoryKey.kt
package com.example.myapplication
import androidx.navigation3.runtime.NavKey
import com.storyblok.cdn.schema.Component
import com.storyblok.cdn.schema.Story
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlin.uuid.Uuid
@Serializable
data class StoryKey(
@Transient
val story: Story<out Component>? = null,
@EncodeDefault
val uuid: Uuid? = story?.uuid,
@EncodeDefault
val slug: String? = story?.slug,
) : NavKey
val HomeKey = StoryKey(slug = "home")

StoryKey is a NavKey that persists the uuid or slug of a story to the back stack. It can also be constructed with a Story instance for immediate display when navigating between screens.

In your MainActivity.kt file, add the code to initialize and use a NavDisplay to render the top of the back stack.

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

The single entry<StoryKey> handles every possible story destination. It fetches the story by UUID or slug, populating the initial value of collectAsStateWithLifecycle with the key’s story instance so available content appears immediately.

With this approach, the project automatically handles new stories that are added to the space and pushed onto the back stack.

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.