Start Flow¶
Before Starting a Predefined Flow¶
If the client code needs to activate the application using the Discovery Service, you can set the Discovery Service URL into DiscoveryServiceProvider
when the application starts.
//in onCreate of application
DiscoveryServiceProvider.set("....")
//in onCreate of application
DiscoveryServiceProvider.set("....");
Then, when the onboard flow comes to the Discovery Service activation step, you only need to input the email domain to get the complete application information published to the Discovery Service.
Note
For detailed information about how to configure the Discovery Service, refer to the online help.
Starting a Flow¶
As of version 3.4, use the following API to start a flow.
/** The callback function signature when starting a flow. */
typealias FlowActivityResultCallback = (requestCode: Short, resultCode: Int, data: Intent?) -> Unit
/**
* Starts a flow and monitors the result using [flowActivityResultCallback]
* @param activity Flow must be started from an existing 'activity' to make sure there is
* already a 'task' running. To start a flow from a fragment, get the activity first then start
* the flow with this.
* @param flowContext The flow context for the flow to be started
* @param flowActivityResultCallback The callback to monitor the flow finishing status. The arguments
* of this callback include 'requestCode' which is the same as in [flowContext], 'resultCode', which
* would either be 'RESULT_OK' or 'RESULT_CANCEL', and an optional 'data' in [Intent]
*/
fun start(
activity: Activity,
flowContext: FlowContext,
flowActivityResultCallback: FlowActivityResultCallback ? = null
) {
...
}
The following two APIs are now deprecated.
@JvmStatic
fun start(activity: Activity, flowContext: FlowContext) {
...
}
@JvmStatic
fun start(fragment: Fragment, flowContext: FlowContext) {
...
}
The reason for this change is that startActivityForResult()
and onActivityResult()
in the Google Android SDK are deprecated and not recommended. We are providing the new API with the recommended solution from Google. Another reason is that, in practice onActivityResult()
cannot be defined for every Activity
and the client code often needs to rely on onFlowFinished
callback in FlowStateListener
to handle the flow finish logic, leading to complex logic in onFlowFinished
. With the newly added API, the client code can provide the flow finish logic when starting the flow.
To simplify migration to the new API, the signature of FlowActivityResultCallback
is the same as that of onActivityResult
, so the client code can remove the keyword 'override' from onActivityResult
, update the function name, and then use it as the callback. For example:
Flow.start(requireActivity(), flowContext, this::onFlowActivityResult)
FlowContext flowContext = new FlowContextBuilder()
.setApplication(new AppConfig.Builder().applicationId("app_id").build())
.setMultipleUserMode(true)
.setFlowStateListener(new MyFlowStateListener())
.setFlowActionHandler(new MyFlowActionHandler())
.setFlowOptions(new FlowOptions() {
@Override
public int getApplicationTheme() {
return R.style.AppTheme;
}
})
.build();
Flow.start(activity, flowContext, (aShort, result, data) -> {
if (result == RESULT_OK) {
startActivity(new Intent(activity, MainBusinessActivity.class));
}
return null;
});
Note
When starting a flow with this API, the onFlowFinished
callback of FlowStateListener
will NOT be called, to prevent the logic of the callback from conflicting with that of onFlowFinished
.
The flowContext
parameter will contain all the necessary information to start the flow, for example, application configuration, flow type, flow options, etc.
data class FlowContext(
/** The application configuration*/
private var appConfig: AppConfig? = null
/** The predefined flow type*/
private var flowType: FlowType = FlowType.ONBOARDING
/** The flow state listener */
private var flowStateListener: FlowStateListener? = null
/** The screen settings */
private var screenSettings: MutableList<ScreenSettings> = mutableListOf()
/** The customized flow */
private var flow: Flow? = null
/** The flow action handler */
private var flowActionHandler: FlowActionHandler = FlowActionHandler()
/** The flow options to control how to show 'info' type message, which activation method to use*/
private var flowOptions: FlowOptions = FlowOptions()
/** Single/Multiple user mode switch */
private var multipleUserMode: Boolean = false
/**
* In the case that the client code wants to use a single activity to start different flows
* and be able to identify which flow finishes in 'onActivityResult', this variable can be
* specified.
*/
val flowRequestCode: Short = FlowConstants.FLOW_ACTIVITY_REQUEST_CODE,
/** The user ID of the user that forgot their passcode. */
val forgotPasscodeUserId: String? = null,
)
The parameters are:
Parameter | Description |
---|---|
appConfig |
The application information that the mobile app uses to talk to mobile services. This is mandatory for onboarding. AppConfig.Builder is provided to build an instance easily. |
enabledServices |
Specify which features the client application wants to use. |
screenSettings |
Customize the screens inside the flow. |
flowStateListener |
Provide customized logic and insert it into the flow execution process at certain points. |
flowActionHandler |
Provide customized logic for certain cases, such as the customize passcode validation logic for example. |
flowOptions |
Pass in options, such as specifying which activation method to use, how to show information message from the flows component, either with a dialog or a toast message. See Flow configuration for details. |
multipleUserMode |
Boolean value to indicate single user or multiple user mode |
flowType |
Predefined flow type, onboarding, reset, etc. |
flow |
Customized flow instance. If this property is provided, flowType will be ignored by the flows component |
flowRequestCode |
The request code that the client code wants to monitor. If the client code wants to start different flows in one activity, this property can be provided. |
forgotPasscodeUserId |
When executing the 'Forgot Passcode' flow, this property must be provided in FlowContext , otherwise, a runtime exception will be reported. |
Sample client code:
val flowContext = FlowContext(
appConfig = AppConfig.Builder().applicationId("app_id").build(),
multipleUserMode = true,
flowStateListener = MyFlowStateListener(),
flowActionHandler = MyFlowActionHandler(),
flowOptions = FlowOptions(
appTheme = R.style.AppTheme
)
)
Flow.start(this@WelcomeActivity, flowContext) { requestCode, resultCode, _ ->
if (resultCode == Activity.RESULT_OK) {
startMainBusinessActivity()
}
}
FlowContext flowContext = new FlowContextBuilder()
.setApplication(prepareAppConfig())
.setFlowStateListener(new WizardFlowStateListener())
.build();
Flow.start(activity, flowContext);
After starting a flow, the flowContext
will be saved into a FlowContextRegistry
. The client code can then get flowContext
from FlowContextRegistry
at a later time in order to update some of the properties and start another flow. For example:
val flowContext = FlowContextRegistry.flowContext.copy(flowType = FlowType.RESTORE)
Flow.start(activity, flowContext) { _, resultCode, _ ->
...
}
FlowContext flowContext = new FlowContextBuilder(FlowContextRegistry.getFlowContext())
.setFlowType(FlowType.CHANGEPASSCODE)
.build();
Flow.start(this, flowContext);
Note
Since Java does not have the 'copy' function on FlowContext
, unlike Kotlin, we can get the FlowContext
instance
from FlowContextRegistry
, then pass it to FlowContextBuilder
to copy the instance and modify the property as needed.
As of SAP BTP SDK for Android 3.3, three new APIs have been added into FlowContext
to retrieve the current user ID, previous user ID, and the offline encryption key.
fun getCurrentUserId(): String?
fun getPreviousUserId(): String?
fun getOfflineEncryptionKey(): String?
The APIs can only be called after the onboarding or restore flow, otherwise null
will be returned. getOfflineEncryptionKey
will only retrieve the key saved in the user secure store, which was saved there during the onboarding process.
getCurrentUserId()
and getPreviousUserId()
will return the same values as the arguments in the callback of onUserSwitched
of FlowStateListener
, so the client code does not need to compare them in the callback function of onUserSwitched
to see whether the user switch occurred or not, then save the status into a variable for later use.
override fun onOfflineEncryptionKeyReady(key: String?) {
with(FlowContextRegistry.flowContext) {
if(getCurrentUserId() != getPreviousUserId()) {
//user switch happened
//Reset OfflineODataProvider, prepare definition queries for the current user
//Upload pending data for previous user, then download data for the current user.
} else {
//open the offline data for the current user
}
}
}
To retrieve the flow execution status when starting the flow with the two deprecated APIs, the client activity or fragment needs to override the onActivityResult
function:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == FlowConstants.FLOW_ACTIVITY_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
//start your main business activity.
startActivity(Intent(this, MainActivity::class.java))
} else {
Snackbar.make(fab, "Flow cancelled.", Snackbar.LENGTH_LONG).show()
}
} else if( requestCode == 100 ) {
//If flowRequestCode is provided in flow context, client code can check the request code here.
}
}
For more information about enabledServices
and screenSettings
in FlowContext
, see Flow Configuration and flowStateListener
and flowActionHandler
in Extension Points.