Skip to content

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.


Last update: November 18, 2021