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"
)
...
}
}
}