CombinedViewModel

Holds the configuration for combined workflows, providing access to the configuration and the state machine that drives the flow as well as the collected results.

Samples

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentTransaction
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.findNavController
import com.miteksystems.misnap.apputil.LicenseFetcher
import com.miteksystems.misnap.R
import com.miteksystems.misnap.core.MiSnapSettings
import com.miteksystems.misnap.databinding.ExampleActivityCombinedWorkflowIntegrationBinding
import com.miteksystems.misnap.workflow.MiSnapErrorResult
import com.miteksystems.misnap.workflow.MiSnapFinalResult
import com.miteksystems.misnap.workflow.MiSnapWorkflowActivity
import com.miteksystems.misnap.workflow.MiSnapWorkflowStep
import com.miteksystems.misnap.workflow.fragment.MiSnapWorkflowViewModel
import com.miteksystems.misnap.workflow.util.CombinedWorkflowHandler
fun main() { 
   //sampleStart 
   /**
 * This is an advanced combined workflow integration example, it is strongly recommended to follow
 * the most simple and easiest combined workflow integration instead:
 * @see com.miteksystems.misnap.examples.activity.IntegrationActivity
 *
 * If the aforementioned example doesn't fit your needs you can integrate the combined workflow in
 * your own activity and delegate the navigation to the MiSnap SDK following this integration
 * example instead:
 * @see com.miteksystems.misnap.examples.workflowhandler.CombinedWorkflowHandlerActivity
 *
 * It is recommended to use this example only when none of the other combined workflow integrations fit
 * your needs. This example is targeted to developers who want to create a combined workflow without
 * delegating the work to the [MiSnapWorkflowActivity] i.e. developers that want to integrate the
 * combined workflow in their own activity while retaining full control of the navigation and logic
 * between steps.
 *
 * The overall process is as follows:
 * 1. Get access to both the [MiSnapWorkflowViewModel] and the [CombinedWorkflowHandler.CombinedViewModel].
 * 2. Subscribe to the [MiSnapWorkflowViewModel] [LiveData]s to handle results and errors.
 * 3. Build the list of [MiSnapWorkflowStep]s and setup the [CombinedWorkflowHandler.CombinedViewModel],
 * set the [MiSnapSettings] of the first step to the [MiSnapWorkflowViewModel].
 * 4. Navigate to start the workflow either by selecting the appropriate navigation graph if using
 * Jetpack Navigation or using [FragmentTransaction]s.
 * 5. Handle the [MiSnapFinalResult]s and the [MiSnapErrorResult]s by adding them as [MiSnapWorkflowStep.Result.Success]
 * or [MiSnapWorkflowStep.Result.Error] respectively to the [CombinedWorkflowHandler.CombinedViewModel].
 * 6. Determine the next [MiSnapWorkflowStep] to use with [CombinedWorkflowHandler.CombinedViewModel.combinedWorkflowStateMachine]
 * and apply its settings to the [MiSnapWorkflowViewModel], repeat steps 4-6 until there are no more steps to handle.
 * 7. Get all the results from [CombinedWorkflowHandler.CombinedViewModel.combinedWorkflowStepResultsList].
 * 8. Clear both [MiSnapWorkflowViewModel] and [CombinedWorkflowHandler.CombinedViewModel].
 *
 * NOTE: When working with the [MiSnapWorkflowViewModel] or [CombinedWorkflowHandler.CombinedViewModel]
 *  it is important to ensure that the view model is acquired through the activity's [ViewModelProvider].
 *
 * NOTE: Ensure that the provided license has all the necessary features enabled for the target
 *  MiSnap session.
 */
class CombinedWorkflowIntegrationActivity : AppCompatActivity() {

    /**
     * Fetch the Misnap SDK license.
     * Good practice: Handle the license in a way that it is remotely updatable.
     */
    private val license by lazy {  
        LicenseFetcher.fetch()
    }

    private lateinit var binding: ExampleActivityCombinedWorkflowIntegrationBinding

    private val misnapWorkflowViewModel: MiSnapWorkflowViewModel by lazy {
        ViewModelProvider(this)[MiSnapWorkflowViewModel::class.java]
    }

    private val combinedWorkflowViewModel: CombinedWorkflowHandler.CombinedViewModel by lazy {
        ViewModelProvider(this)[CombinedWorkflowHandler.CombinedViewModel::class.java]
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ExampleActivityCombinedWorkflowIntegrationBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }

    override fun onStart() {
        super.onStart()

        /**
         * Create the list of [MiSnapWorkflowStep]s in the order you want them to appear in the
         * combined workflow.
         */
        val stepsList = listOf(
            MiSnapWorkflowStep(MiSnapSettings(MiSnapSettings.UseCase.PASSPORT, license).apply {
                analysis.document.trigger = MiSnapSettings.Analysis.Document.Trigger.AUTO
            }),
            MiSnapWorkflowStep(MiSnapSettings(MiSnapSettings.UseCase.BARCODE, license).apply {
                analysis.barcode.type = MiSnapSettings.Analysis.Barcode.Type.QR_CODE
                analysis.document.barcodeExtractionRequirement =
                    MiSnapSettings.Analysis.Document.ExtractionRequirement.REQUIRED
                analysis.barcode.trigger = MiSnapSettings.Analysis.Barcode.Trigger.AUTO
            }),
            MiSnapWorkflowStep(MiSnapSettings(MiSnapSettings.UseCase.NFC, license))
        )

        /**
         * Observe [MiSnapWorkflowViewModel.results] for [MiSnapFinalResult]s and add them as
         * [MiSnapWorkflowStep.Result.Success]. There should be exactly one result per step
         * regardless of the result type.
         */
        misnapWorkflowViewModel.results.observe(this) {
            it?.let {
                combinedWorkflowViewModel.addCombinedWorkflowStepResult(
                    MiSnapWorkflowStep.Result.Success(it)
                )

                // Once collected, evaluate the steps to determine the next action.
                processNextStep()
            }
        }

        /**
         * Observe [MiSnapWorkflowViewModel.error] for [MiSnapErrorResult]s and add them as
         * [MiSnapWorkflowStep.Result.Error]. There should be exactly one result per step
         * regardless of the result type.
         */
        misnapWorkflowViewModel.error.observe(this) {
            it?.let {
                combinedWorkflowViewModel.addCombinedWorkflowStepResult(
                    MiSnapWorkflowStep.Result.Error(it)
                )

                // Once collected, evaluate the steps to determine the next action.
                processNextStep()
            }
        }

        /**
         * Configure the [CombinedWorkflowHandler.CombinedViewModel] by applying the list of [MiSnapWorkflowStep]s
         * and verifying its configuration. Then set the [MiSnapSettings] of the initial step to the
         * [MiSnapWorkflowViewModel] to complete the start setup.
         *
         * Once ready, handle the navigation to start the workflow.
         */
        combinedWorkflowViewModel.applyCombinedWorkflowSteps(stepsList)
        if (combinedWorkflowViewModel.isCombinedWorkflowConfigured()) {
            misnapWorkflowViewModel.applySettings(stepsList.first().settings)

            navigateToStep(stepsList.first().settings.useCase)
        } else {
            // Handle an invalid combined workflow configuration.
        }
    }

    private fun processNextStep() {
        // Use the combined workflow state machine to determine the next action.
        combinedWorkflowViewModel.combinedWorkflowStateMachine?.let { workflowStateMachine ->
            val results = combinedWorkflowViewModel.getCombinedWorkflowStepResultsList()
            val steps = combinedWorkflowViewModel.combinedWorkflowStepsList

            /**
             * Determine the next and previous step, as well as the last results.
             * At the end of all steps there should be one result per step.
             */
            val latestResults = results.last()
            val latestStep = steps[results.size - 1]
            val nextStep = steps.getOrNull(results.size)

            /**
             * Determine the next [CombinedWorkflowHandler.StateMachine.Action].
             */
            when (val nextAction =
                workflowStateMachine.processNextState(latestResults, latestStep, nextStep)) {
                is CombinedWorkflowHandler.StateMachine.Action.Next -> {
                    // Continue to the next step.
                    val nextStepSettings = nextAction.step.settings

                    // Clear the viewmodel between steps.
                    misnapWorkflowViewModel.clearResults()

                    /**
                     * Handle your custom logic, e.g. by mutating the settings based on other results
                     * or skipping steps based on business rules.
                     */
                    misnapWorkflowViewModel.applySettings(nextStepSettings)

                    // Navigate to the next step.
                    navigateToStep(nextStepSettings.useCase)
                }
                is CombinedWorkflowHandler.StateMachine.Action.Skip -> {
                    /**
                     * Handle steps that should be skipped, e.g. by adding them as an error result
                     * before moving to the next step.
                     */
                }
                is CombinedWorkflowHandler.StateMachine.Action.Finish -> {
                    /**
                     * Handle the end of the workflow e.g. by collecting all the results and
                     * cleaning the view models.
                     */
                    val allResults = combinedWorkflowViewModel.getCombinedWorkflowStepResultsList()

                    combinedWorkflowViewModel.resetViewModel()
                    misnapWorkflowViewModel.clearResults()
                }
            }
        }
    }

    /**
     * This example handles the navigation using Jetpack Navigation and the built-in navgraphs.
     * Alternatively, you can handle the navigation with your own custom navgraphs or manually by
     * using [FragmentTransaction]s.
     *
     * @see com.miteksystems.misnap.examples.fragment.AnalysisFragmentTransaction for an example on
     * how to drive a workflow using [FragmentTransaction]s.
     */
    private fun navigateToStep(useCase: MiSnapSettings.UseCase) {
        // Select the most appropriate navgraph based on the use case.
        val navGraph = when (useCase) {
            MiSnapSettings.UseCase.PASSPORT -> {
                R.navigation.document_session_flow
            }
            MiSnapSettings.UseCase.BARCODE -> {
                R.navigation.barcode_session_flow
            }
            MiSnapSettings.UseCase.NFC -> {
                R.navigation.nfc_reader_flow
            }
            else -> {
                throw IllegalArgumentException()
            }
        }

        // Set the navigation graph.
        findNavController(R.id.fragmentContainer).setGraph(navGraph)
    }
} 
   //sampleEnd
}

Constructors

Link copied to clipboard
constructor()

Functions

Link copied to clipboard

Adds an entry to the list of collected results.

Link copied to clipboard

Resets the ViewModel and configures the workflow by setting the list of steps and assigning a state machine.

Link copied to clipboard

Verifies that there is a valid combined workflow configuration.

Link copied to clipboard

Resets the ViewModel to a clean state, removing all results collected, configurations and the state machine.

Properties

Link copied to clipboard

The state machine that drives the combined workflow.

Link copied to clipboard

The list of steps currently configured for the combined workflow.