Skip to content

Start Flow

Starting a Flow

There are two APIs in FlowUtil you can use to start a flow.

    fun startFlow(
        context: Context,
        flowContext: FlowContext,
        updateIntent: ((Intent) -> Unit)? = null,
        callback: FlowActivityResultCallback = { _, _ -> }
    ) { ... }

    fun startRestoreIfLocked(
        context: Context,
        flowContext: FlowContext,
        updateIntent: ((Intent) -> Unit)? = null,
        callback: FlowActivityResultCallback = { _, _ -> }
    ) { ... }

The signature of these two APIs is the same. The first one is the most common one to use for most cases, for example, starting the change passcode flow after the onboarding or restore flow:

    FlowUtil.startFlow(
        context,
        flowContext = DemoApplication.getOnboardingFlowContext(context)
    ) { resultCode, _ ->
        if (resultCode == Activity.RESULT_OK) {
            val intent = Intent(this, MainActivity::class.java).apply {
                addFlags(
                    Intent.FLAG_ACTIVITY_NEW_TASK or
                            Intent.FLAG_ACTIVITY_CLEAR_TASK or
                            Intent.FLAG_ACTIVITY_CLEAR_TOP
                )
            }
            startActivity(intent)
        } else finish()
    }

The second API is used to start the restore flow only if the app is locked at the time of its being called. For example, when you're on the widget configuration activity to create a widget, you may want to call the API to check the app's lock status. If the app is not locked, this API will return immediately. If the app is locked, then it will bring the restore flow back, then the widget configuration activity. For example:

    private fun startOnboarding(callback: FlowActivityResultCallback) {
        FlowUtil.startRestoreIfLocked(
            this@ODataWidgetConfigurationActivity,
            DemoApplication.getOnboardingFlowContext(
                this@ODataWidgetConfigurationActivity
            ),
            callback = {
                if (resultCode == Activity.RESULT_CANCELED) {
                    setResult(resultCode)
                    finish()
                }
            }
        )
    }

Arguments

This section describes the arguments that are needed to start a flow. context could be your activity to start a flow. The following sections explain the other arguments.

FlowContext

    data class FlowContext(
        val flowType: FlowType = FlowType.Onboarding,
        val flow: BaseFlow? = null,
        val appConfig: AppConfig = <Default AppConfig>,
        val flowOptions: FlowOptions = FlowOptions(),
        val flowStateListener: FlowStateListener = FlowStateListener(),
        val flowActionHandler: FlowActionHandler = FlowActionHandler()
    ) {
        fun getCurrentUser(): DeviceUser? = currentUser
        fun getPreviousUser(): DeviceUser? = previousUser
    }

FlowContext is almost the same as that in the view-based flows, except that the screen settings property is moved into FlowOptions.

Use flowType for predefined flows and flow for customized flows. When flow is provided, flowType will be ignored by the flows framework.

Use FlowOptions to customize the flow. FlowActionHandler allows the client code to inject logic for the onboarding process, for example, to add custom steps. FlowStateListener allows the client code to observe the states during the onboarding or restore flow.

AppConfig now has a default value. If the client needs to activate the app with either the Discovery service, QR code, or the managed configuration, you don't need to provide AppConfig as is required in the view-based flow.

An additional difference is that there is no 'builder' in FlowContext, you can simply initialize this 'data' class, or use the 'copy' function of the Kotlin 'data' class.

FlowActivityResultCallback

The definition of FlowActivityResultCallback is:

typealias FlowActivityResultCallback = (resultCode: Int, data: Intent?) -> Unit

The difference between this and the view-based flow is that there are only two arguments (requestCode was removed). It has also been marked as deprecated in the view-based flow because it was designed before FlowActivityResultCallback was introduced in the SDK and onActivityResult was the only way to observe the flow finish status.

The other difference is that FlowActivityResultCallback is not nullable and now has a default value.

updateIntent

This argument is not available in the view-based flows component. It is used mainly for the following cases:

Add Intent Flags

In some cases, additional intent flags might be needed to start a flow. For example, when testing your own flow, or starting the predefined flow from a widget, INTENT.FLAG_ACTIVITY_NEW_TASK may be required. See the test code below:

    @Test
    fun test_restore_flow_no_passcode() = runBlocking {
        rule.setContent {
            prepareScreen {
                Button(onClick = {
                    FlowUtil.startFlow(applicationContext, flowContext = flowContext,
                        updateIntent = {
                            it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                        }) { _, _ -> }
                }) {
                    Text("StartRestore")
                }
            }
        }
        rule.onNodeWithText("StartRestore").performClick()
        ...
    }

Initial Data

Besides handling the onboarding processes, the flows component (both the view-based and the compose versions) can also be used as a framework to host custom flows. That is, you can develop your own flow and start it with the flows framework. In some cases, the custom flow may need to pass in some initial data to be shared by all the steps in this flow.

To do this in the view-based flow, you can override the getInitialData function in your flow. In the compose version, you can do this using the prepareCustomBundle function in FlowUtil. For example:

    FlowUtil.startFlow(context, flowContext = flowContext, updateIntent = { intent ->
        FlowUtil.prepareCustomBundle(intent) {
            putString("my_custom_prop_1", "Custom Property 1")
        }
    }) { code, _ ->
        ...
    }

You can retrieve the my_custom_property_key property value in your flow using the following code:

    ...
    addSingleStep("step_2") {
        OnboardingScreenContainer {
            OnboardingScreenTitleSection()
            Column(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(24.dp),
                verticalArrangement = Arrangement.Center
            ) {
                Text(
                    text = this@DemoCustomFlow.getCustomBundle()?.getString("my_custom_prop_1")
                        ?: "No Such Property"
                )
                ...
            }
        }
    }

Last update: February 20, 2023