Technical Articles
FlowStateListener in the Flows Component of SAP BTP SDK for Android
Introduction
In the previous blog post, we discussed how simple the Flows component of SAP BTP SDK for Android was to use in client code. In this article, we’re going to dive deeper into the Flows component regarding the different states during the onboarding, restore, and other flows, and how the client code can listen to these states and execute its own logic.
Flow States
During the onboarding, restore, and other flows, the Flows component will collect different information at different times. For example, the application configuration will be ready after activation steps are completed, usage collection consent status will be ready after the consent step is completed, etc. These states need to be passed to the client code for different purposes. For example:
- The client code initializes the OData online or offline data provider when the application configuration is ready.
- When the passcode is set, the client code wants to use the passcode to encrypt its own secure store.
- When ‘OkHttpClient’ is ready, the client code wants to add its own HTTP headers.
- When user information is retrieved from the mobile server, the client code wants to prepare the definition query of the offline OData for this user.
- When the offline OData encryption key is ready, the client code uses it to prepare the offline OData parameters and initialize the offline OData data provider.
Listen to the States
To listen the states of each flow, you can create an instance of your ‘FlowStateListener’ and set it into ‘FlowContext’ when you start the flow. For example:
//Kotlin
class WizardFlowStateListener(private val application: SAPWizardApplication) :
FlowStateListener() {
……
}
//In your activity
Flow.start(activity, flowContext = FlowContext(
appConfig = appConfig,
flowStateListener = WizardFlowStateListener(application)
))
Onboarding States
The diagram below shows all the states during the onboarding process, when they are notified to the client code, and their sequence.
Onboarding Flow States
- Aside from the Authentication and Passcode/Biometric enablement steps, all other steps are optional. We will talk about how the Flow component decides whether to require a particular step(?) step or not. Configuring the flow will be covered in a subsequent article.
- Application configuration will be ready before the authentication. The Flow component will notify the client of this using the ‘onAppConfigRetrieved’ function in ‘FlowStateListener’.
- Before authentication, ‘OkHttpClient’ will be ready and sent to the client code using ‘onOkHttpClientReady’.
- After authentication is completed, the Flow component will get the client policies from the mobile server and then notify the client code using ‘onClientPolicyRetrieved’.
- If the passcode policy is enabled, the Flow component will show the passcode enablement steps to collect the passcode and then notify the client code using ‘onPasscodeUpdated’. This function will contain both the new and optional old passcode information, so that the client code will know that this is either the very first time to enable the passcode on the secure store, or that this is a change passcode case. And yes, this function will also be called in the ‘Change Passcode’ flow.
- If the passcode policy is not enabled, the Flow component will not contain the UI steps for passcode and biometric collection, but the child flow, ‘SetPasscodeFlow’, will always be present in onboarding, so that after it’s finished, the user information will be notified to the client code using ‘onUserSwitched’, and if the Flow component is used in an OData offline application, the Flow component will also call the SDK API to get the offline OData store encryption key and notify the client code using ‘onOfflineEncryptionKeyReady’.
- Usage collection and crash report upload consent status will be notified to the client code with ‘onConsentStatusChange’. Since there will be more than one step to collect the consent status, this function in ‘FlowStateListener’ may be called multiple times
- ‘onFlowFinished’ will always be called after the flow is finished. For predefined flows, this function will contain the flow name information. We will talk about how to define your own flow using the Flow framework in another article.
Restore States
After onboarding, when the app is restarted, the restore flow will kick in. Then, you may ask, if I initialize my online/offline OData provider in ‘onAppConfigRetrieved’, prepare definition queries for a user in ‘onUserSwitched’, and offline OData parameters in ‘onOfflineEncryptionKeyReady’ during onboarding, will these events also be notified during restore?
The answer is yes.
The restore flow will basically consist of biometric or passcode unlock steps. After the app is successfully unlocked, either with biometric or passcode data, all the events mentioned above will be notified to the client code. The restore states consist of the following:
Restore Flow States
- ‘onAppConfigRetrieved’ will be called when the restore flow is started, because Flow saved it in the secure database during onboarding.
- ‘onOkHttpClientReady’ will be called to send the ‘OkHttpClient’ instance to the client code.
- If the app is unlocked using a passcode, ‘onUnlockWithPasscode’ will be called. There is no ‘onUnlockWithBiometric’ in ‘FlowStateListener’ because, after the cipher returned from the Android system is used to unlock the user secure store managed by Flow, it cannot be passed out to be used again. So, we recommend that the client code not maintain its own user secure store. Instead, client code can save and retrieve data from the user secure store managed by Flow with the provided API, ‘UserSecureStoreDelegate’. If the client code wants to have its own secure store, we recommend that the client code generate a random passcode and save it into the secure store managed by the Flows component during onboarding using ‘onPasscodeUpdated’, then retrieve it in ‘onFlowFinished’ after the restore flow to open the secure store.
- After the app is unlocked successfully, the restore flow will make sure ‘onUserSwitched’ is called before ‘onOfflineEncryptionKeyReady’, the same way as in the onboarding process. ‘onClientPolicyRetrieved’ and ‘onConsentStatusChange’ will also be called.
- ‘onFlowFinished’ is called after the flow is completed.
Other Callbacks in FlowStateListener
Onboarding and restore flow cover most of the callback functions in ‘FlowStateListener’, leaving only two additional callbacks:
- ‘onApplicationReset’. This will only be called in the reset flow. The Flow component will call your logic first and then remove the data managed by Flow. For example, when resetting the application, if you want to unregister the push token, this is the right place to do it. Please make sure no UI related logic in this callback.
- ‘onApplicationLocked This will only be called in a flow that handles the case where the app is put into background for a while and then brought up to the foreground sign-in screen when needed.
Sample Code
Suppose the client code wants to add an HTTP header into ‘OkHttpClient’, it can override ‘onOkHttpClientReady’ in its ‘FlowStateListener’ in Java:
@Override
public void onOkHttpClientReady(@NotNull OkHttpClient httpClient) {
OkHttpClient newClient = FlowUtilKt.addUniqueInterceptor(httpClient, chain -> {
Request request = chain.request();
Request newRequest = request.newBuilder()
.header("my_header", "my_header_value")
.build();
return chain.proceed(newRequest);
});
}
For Kotlin client, it will look like this:
override fun onOkHttpClientReady(httpClient: OkHttpClient) {
httpClient.addUniqueInterceptor( object: Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request: Request = chain.request()
val newRequest = request.newBuilder()
.header("my_header", "my_header_value")
.build()
return chain.proceed(newRequest)
}
})
}
‘addUniqueInterceptor’ is an extension function provided by the Flows component, which adds unique interceptors into ‘OkHttpClient’ since the above callback function may be called multiple times.
Summary
When you implement your own ‘FlowStateListener’, be aware of the following:
- ‘onFlowFinished’ will run on the UI thread. All other callbacks will run on the I/O thread.
- The callback implementation should be lightweight. It’s not recommended for it to contain complex logic.
- Please handle exceptions in these callbacks carefully, because the Flow component will ignore them to ensure they do not crash the app.
Please leave your feedback here in the comment section below. For questions about SAP BTP SDK for Android, please leave them here: https://answers.sap.com/tags/73555000100800001281