NavFlow scripts let you orchestrate complex flows as coroutines: showRoot, pushNode, wait for outputs, emit trace logs, and finish when the story is done. They leave nodes focused on UI state.

Launching a Script

class OnboardingViewModel(
    private val repository: AuthRepository
) : ViewModel() {
    val navFlow = NavFlow(viewModelScope, SplashNode(viewModelScope)).apply { start() }

    private val job = navFlow.launchNavFlowScript(viewModelScope, onTrace = { println(it) }) {
        showRoot { SplashNode(viewModelScope) }
        awaitOutputOfType<OnboardingOutput.SplashFinished>()

        showRoot { SignInNode(viewModelScope) }
        when (awaitOutputOfType<OnboardingOutput.AuthResult>()) {
            OnboardingOutput.AuthResult.Success -> showRoot { DashboardNode(viewModelScope) }
            is OnboardingOutput.AuthResult.Error -> showRoot { ErrorNode(viewModelScope) }
        }
    }

    override fun onCleared() {
        job.cancel()
        super.onCleared()
    }
}

Script Scope Helpers

Inside NavFlowScriptScope you get:

Helper Description
showRoot { node }, pushNode { node }, replaceTop { node } Stack operations without touching navigator internals.
awaitOutputOfType<T>(), awaitOutputCase { … }, awaitMappedOutput { … } Wait for future outputs.
withNode { … }, pushForResult { … } Push a temporary node, run work, pop automatically.
runCatchingNodeCall Show loading/success/error states while performing suspend work.
trace { "message" } Emit debug logs via the onTrace callback passed to launchNavFlowScript.

Testing Scripts

Use FlowTestScenario:

val scenario = factory.createTestScenario(this).start()
val job = scenario.launchScript { runContactsScript(fakeRepo, this@runTest) }
scenario.awaitTopNodeIs<ContactDetailsNode>()
job.cancelAndJoin()

For full recipes (contacts flow, checkout, retries, reusable sub-flows) see the Cookbook.