Onboarding - application lifecycle

Managing the Application Lifecycle with OnboardingSession and OnboardingSessionManager

OnboardingSessionManager can be a central point for an application to access OnboardingSession, which contains all services required by the application to run properly, for example:

  • SAPURLSession assigned to an authenticated user,
  • Secure credential store for sensitive information,
  • OData services, etc.

OnboardingSession can be used for common tasks, for instance:

  • locking/unlocking the application,
  • perform base operations, for example log upload,
  • managing the passcode of the secure store using StoreManager.

OnboardingSession can be created and used without OnboardingSessionManager however the recommended way is to use it.

With OnboardingSessionManager, the whole life cycle of an application can be supported:

  • at first run, perform a user onboarding,
  • locking the application UI (by hiding the application screens containing sensitive data) when the running application goes to background,
  • unlocking the application UI (using passcode screen) when the application comes back to foreground,
  • restoring the user onboarding session when the application has been restarted - and had been stopped in an onboarded state,
  • removing the user onboarding session wiping all user related data.

The general set up, relation of the related components and participants, and the rule they have in the system is the following:

  • Application uses OnboardingSessionManager to manage an OnboardingSession and follow the application lifecycle changes.
  • OnboardingSessionManager
    • uses OnboardingController to manage the OnboardingSession; opens an OnboardingFlow which runs successfully, then converts the result to an OnboardingSession. It is the OnboardingController‘s concern to decide which flow should run - more precisely it is the OnboardingIDManaging implementation assigned to OnboardingController,
    • the session manager calls OnboardingController to remove an existing session,
    • it sets its state and calls appropriate delegate methods to manage UI in case the application goes to background or comes to foreground. The appropriate methods should be called by Application.
    • The session manager does not manipulate the application UI directly. It makes changes related to the application UI using OnboardingSessionManagerUIManaging protocol, which should be implemented by application developers. This way application developers have full control over UI elements, and how they are being used - which view controller is presented for a particular action and how it is presented.
  • OnboardingController
    • calls its onboarding ID manager to decide what kind of flow should be started: creating a new one using onboard or restoring an existing one using restore.
    • then, calls the onboarding flow provider to provide the required flow. It must be implemented by application developers,
    • finally, it performs the proper method on the flow.
  • Onboarding ID manager should conform to the OnboardingIDManaging protocol.
    • Implementation decides what flow should be started based on its state and supported functionality - and optionally based on user interaction (selection).
    • It also has to store OnboardingIDs so they can be retrieved later after a restart.

Logging out

Log out can mean different things to different applications and there are more methods to support it. Different cases are:

  • Log out from the server: invalidating the tokens while keeping data saved locally. In this case, a new request should trigger an authentication if the SAPURLSession is properly configured with authentication observers.
  • Log out locally, closing session, user switch: closing the onboarding session in the application making sure that the SAPURLSession and the secure store is not used. The session however remains on the device and can be restored with the common rules. A new onboarding session must be created to make the application usable:
    • an existing flow must be restored or
    • a new flow should be created. If there is a previous session persisted, then it can be supported only when multi-user scenario is supported. For that, at least the OnboardingIDManaging in OnboardingController should be replaced with a custom one,
  • remove onboarding session: the session and all its local data will be removed from the device and cannot be restored.

Using OnboardingSession, all the use-cases are supported.

  • Log out on server: CPms supports log out functionality. It invlidates the authentication of the user. Call OnboardingSession’s logout method.
  • Log out locally, closing the session: just release the reference to the OnboardingSession and create a new one. There is an invalidate method on the OnboardingSession, which invalidates the session. Can be useful when the session is shared among objects to make sure it won’t be usable after it has been invalidated. Using OnboardingSessionManager, the easiest way is to call close which invalidates the existing session and then releases it.
  • Log out from server then locally: just perform the two steps mentioned above: call logout on the session then invalidate it.
  • Removing session: there can be two cases:

Multi-User Support

SAPFioriFlows also supports multi-user scenarios. Multi-user can mean different things in various contexts. An application might have multiple types of flows, or it can simply mean that a single flow is being used with more than one onboarded users – allowing each user to access their own data securely.

As an application developer, you are free to implement this use-case as desired. However, we recommend to use the OnboardingSessionManager with a custom implementation of the OnboardingIDManaging protocol – by default, we provide a SingleOnboardingIDManager only, since multi-user scenarios can be more complex.

You can use many different sources of data structures to read, write and store your users’ OnboardingIDs. You might store them on the users’ device in the UserDefaults, you may want to store them in the KeyChain or even in a database. It is up to the application developer to decide what suits their needs. For storing and removing data related to the OnboardingID, you have to implement the store(onboardingID:completionHandler:) and remove(onboardingID:completionHandler:) methods.

Also, it is your concern to determine if you want to start an onboarding, restoring or resetting flow – in other words: how you want to implement flowToStart(completionHandler:) – and what logic you wish to use for this purpose.

A simple way to do this is to check if an OnboardingID already exists in your data set. If it does, you know you are dealing with a restore flow. Otherwise, you can return .onboard as the flow to start. You can retrieve an OnboardingID from the users by displaying a view controller where they can select an existing OnboardingID or start a new session – resulting in a new OnboardingID.

You will also have to provide some UI elements for the end-user where they can select a given OnboardingID (if it exists), or create a new onboarding session – and connect this UI to your custom implementation of OnboardingIDManaging.

User Deletion and Data Cleanup

In some application flows, it may be necessary to delete a user from the app. For example, this could occur when a user logs out or is deactivated in the app. In such scenarios, it is critical to ensure that all user-related data is also removed to maintain data consistency and privacy.

The SAP BTP SDK for iOS provides specific APIs to facilitate proper user deletion and data cleanup. This document outlines the recommended methods and approaches to ensure the application remains in a valid state after user removal, whether the app supports single or multiple users.

Single-User Applications

For applications that support only one user at a time, deleting the user should trigger a UI reset and return the app to its initial onboarding state.

The following method can be used:

OnboardingSessionManager.shared.removeSession {
    Task {
        // Open a new flow which resets the UI to onboarding
        try await OnboardingSessionManager.shared.open()
    }
}

Alternatively, if using code from an Assistant-generated application, you can invoke the resetOnboardingSessionManager() method provided by the OnboardingErrorHandler, which performs the same operation.

OnboardingErrorHandler.resetOnboardingSessionManager()

Multi-User Applications

In multi-user scenarios, there are two primary cases to handle:

Case 1: A User Deletes Themselves

When a user decides to delete themselves from the app (regardless of how many users are currently onboarded), the SDK recommends the following approach:

OnboardingSessionManager.shared.removeSession {
    self.afterReset()
}

Alternatively, you can manually delete the user and reset the onboarding flow as shown:

// Get onboardingID of the user to be deleted
let onboardingID: UUID 

// Delete the user
UserManager().delete(forKey: onboardingID)

// Reset the onboarding flow for the deleted user
OnboardingSessionManager.shared.onboardingController.resetFlow(for: onboardingID) { error in
    guard error == nil else {
        return
    }
    self.afterReset()
}

The afterReset() method ensures that the application state and UI reflect the current user base:

func afterReset() {
    let onboardedUsers = UserManager().getUsers(isActive: true)

    if onboardedUsers.isEmpty {
        print("Starting onboarding flow...")
    } else {
        UserManager.transientUser = onboardedUsers.first! // any of the onboarded user can be used
        UserManager.transientUser?.onboardingStatus = .restoreInProgress
        print("Starting restore flow for user: \(UserManager.transientUser!.id)")
    }

    OnboardingSessionManager.shared.state = .initial
    // Start new onboarding or restore flow
    Task {
        try await OnboardingSessionManager.shared.open()
    }
}

Case 2: One User Deletes Another

In scenarios where one user (e.g., an admin) deletes another user, the flow is similar but does not require a UI reset for the current user. Use the following code:

// Get onboardingID of the user to be deleted
let onboardingID: UUID 

// Delete the specified user
UserManager().delete(forKey: onboardingID) 

// Reset the onboarding flow for the deleted user
OnboardingSessionManager.shared.onboardingController.resetFlow(for: onboardingID) { error in
    guard error == nil else {
        return
    }
}

Note: The code examples provided are based on an assistant-generated application. It is assumed that SAPFioriFlows is imported in the app.

  • Stores and manages one user’s OnboardingSession

    See more

    Declaration

    Swift

    public class OnboardingSessionManager<T> where T : OnboardingSession
  • Stores information about an onboarding session Application developer can subclass OnboardingSession to add other app related meaningful properties or to set properties properly. For example when the SAPcpmsSettingsParameters is not stored using the default key in OnboardingContext then subclasses have to implement the code to provide the SAPcpmsSettingsParameters based on the used key

    open class YourSession: OnboardingSession {
        var appURL: URL!
        var otherIntProperty: Int = 0
        var appDict = [String: Any]()
    
        required public init(flow: OnboardingFlow) {
           super.init(flow: flow)
    
            // When SAPcpmsSettingsParameters is under another key
            self.settingsParameters = flow.context.info[.yourSettingsParameters] as? SAPcpmsSettingsParameters
    
            self.appURL = URL(string:"")
            self.otherIntProperty = 12
            self.appDict = ["a":"b"]
        }
    }
    

    When a custom OnboardingInfoKey needs to be used to store a custom value, then the corresponding OnboardingSession subclass may be implemented as follows:

    public extension OnboardingInfoKey {
        static let yourCustomOnboardingInfoKey = OnboardingInfoKey("yourCustomOnboardingInfoKey")
    }
    
    class CustomOnboardingStep: OnboardingStep{
         func onboard(context: OnboardingContext, completionHandler: @escaping (OnboardingResult) -> Void) {
             var newContext = context
    
             // set value under newly added key inside Info dictionary
             newContext.info[.yourCustomOnboardingInfoKey] = "AnyStringValue"
    
             completionHandler(.success(newContext))
         }
    
         func restore(context: OnboardingContext, completionHandler: @escaping (OnboardingResult) -> Void) {
             var newContext = context
    
             // set value under newly added key inside Info Dictionary
             newContext.info[.yourCustomOnboardingInfoKey] = "AnyStringValue"
    
             completionHandler(.success(newContext))
         }
    
         func reset(context: OnboardingContext, completionHandler: @escaping () -> Void) {
             completionHandler()
         }
    
    }
    
    open class YourSession: OnboardingSession {
    
        var yourCustomProperty: Any = ""
    
        required public init(flow: OnboardingFlow) {
            super.init(flow: flow)
    
            // set value of custom property here
            yourCustomProperty = flow.context.info[.yourCustomOnboardingInfoKey]
        }
    }
    
    //Use the code below to access it
    OnboardingSessionManager.shared.onboardingSession?.yourCustomProperty
    
    See more

    Declaration

    Swift

    open class OnboardingSession
  • Protocol for onboarding controller implementations, used by OnboardingSessionManager

    See more

    Declaration

    Swift

    public protocol OnboardingControlling
  • Onboards or restores an OnboardingFlow based on the response of the given OnboardingIDManaging implementation.

    See more

    Declaration

    Swift

    public class OnboardingController : OnboardingControlling
  • Implementers should manage the onboarding ID of the onboarding sesssions.

    See more

    Declaration

    Swift

    public protocol OnboardingIDManaging : AnyObject