Launch Spend Manager

Basic Integration

An integration with default options only requires a host UIViewController to be provided. In this case Sensibill UI will appear with animation, and will be presented modally, over the provided host. It will start on Dashboard view.

You should also set a SensibillUICoordinatorDelegate and implement its required protocol function coordinatorWillTerminateWebUI

Step 1: Define and retain an instance of SensibillUICoordinator

private var sensibillUICoordinator: SensibillUICoordinator?

Step 2: Instantiate SensibillUICoordinator by specifying host of type UIViewController. Specify delegate and invoke start function.

Following code snippet will attempt to start spend manager in modal presentation mode on provided host and lands on default view i.e. dashboard.

sensibillUICoordinator = SensibillUICoordinator(host: self)
sensibillUICoordinator?.delegate = self
sensibillUICoordinator?.start()

Step 3: Implement required functions of SensibillUICoordinatorDelegate protocol and cleanup local instance of SensibillUICoordinator when notified via coordinatorWillTerminateWebUI(_:) function.

extension MyViewController: SensibillUICoordinatorDelegate {

    // Called when the SDK requests termination
    func coordinatorWillTerminateWebUI(_ coordinator: SensibillUICoordinator) {
        // Clean up the sensibillUICoordinator
        sensibillUICoordinator = nil
    }
}

Startup Customization Options

Presentation Methods

The Sensibill UI can be presented in 3 different ways:

  • Modal (default)
  • Embed
  • Push

The presentation method can be specified in SensibillUICoordinator initializer by specifying one of the value of PresentationMethod for presentationMethod parameter.

This option presents a new UINavigationController with Sensibill’s UIViewController set as its root view. The navigation controller uses the integrator specified host view controller as its PresentingViewController.

To present the UI modally, no additional options are required, as it is the default value. If you wanted to explicitly include it for clarity you can do the following:

private var sensibillUICoordinator = SensibillUICoordinator(host: self, presentationMethod: .modal)
sensibillUICoordinator.start()

Embed

The embed presentation option will embed Sensibill’s NavigationController into the provided host UIViewController. Sensibill’s NavigationController will be added as the top-most subview in the provided host controller, and will constrain itself to fill the full size of the host controller’s view.

Note: This presentation method requires the integrator to manually dismiss Sensibill’s UI as outlined in ‘Handling a termination request’ below.

let sensibillUICoordinator = SensibillUICoordinator(host: self, presentationMethod: .embed)
sensibillUICoordinator.start()

Push

The push presentation option will push Sensibill’s UIViewController’s UIView onto the provided host navigation controller’s navigation stack. This method requires the host view controller be a UINavigationController, or else it will default to a modal presentation.

Note: This presentation method requires the integrator to manually dismiss Sensibill’s UI as outlined in Handling a termination request below.

let sensibillUICoordinator = SensibillUICoordinator(host: self, presentationMethod: .push)
sensibillUICoordinator.start()

Animation

By default the animation on appearance is enabled. You can disable the animation by setting it to false in the start function:

let options = SensibillUICoordinator.StartOptions.Builder()
              .add(animated: false)
              .build()
sensibillUICoordinator.start(options: options)

Initial Page

By default, the Sensibill UI will start on Dashboard page. If you would like to start Sensibill UI on a different page, specify the navigationIntent argument of the start:

let options = SensibillUICoordinator.StartOptions.Builder()
              .add(navigationIntent: .dashboard)
              .build()
sensibillUICoordinator.start(options: options)

See the Starting On a Specific Page page for more information.

Launching Spend Manager to Edit Receipt Metadata

You can launch Spend Manager upon completion of the Capture to allow a user to specify folders and notes for the captured receipts. For more information, see Launching Spend Manager to Edit Receipt Metadata .

Handling A Termination Request

If a SensibillUICoordinator is initialized with either the push or embed presentation mode , it will not know how to safely dismiss itself. This requires the integrator to handle the dismissal of Sensibill’s UI manually inside of the coordinatorWillTerminateWebUI delegate function.

Example:

func coordinatorWillTerminateWebUI(_ coordinator: SensibillUICoordinator) {

    // sensibillUIPresentationMethod can be local variable storing current presentation mode used to launch sensibillUICoordinator

    switch sensibillUIPresentationMethod {
    // Remove the SensibillUI here

    // EXAMPLES:
    switch webUIPresentationMethod {
        case .embed:
            referenceToSensibillUIViewController.view.removeFromSuperview()
            referenceToSensibillUIViewController.removeFromParent()
            referenceToSensibillUIViewController.didMove(toParent: nil)
        case .push:
            myUINavigationController.popToRootViewController(animated: true)
    }

    // Required clean up
    sensibillUICoordinator = nil
}

Optional Delegate Functions

The SensibillUICoordinatorDelegate provides one or more optional functions you can implement to be notified about, and act upon, key events that occur within the SDK.

Will Present Sensibill UI

These functions are called before and after Sensibill’s UI is presented. They provide the integrator the opportunity to access a reference to the presented UINavigationController to apply any desired customization.

func coordinator(_ coordinator: SensibillUICoordinator, willPresent webUINavigationController: UINavigationController) {

    // Declare Bar Button Item
    let leftBarButtonItem = UIBarButtonItem(...)

    // You can add BarButtonItems
    webUINavigationController.navigationBar.topItem?.leftBarButtonItem = leftBarButtonItem

    // Or hide the NavigationBar altogether
    webUINavigationController.setNavigationBarHidden(true, animated: false)
}

Did Present Sensibill UI

func coordinator(_ coordinator: SensibillUICoordinator, didPresent webUINavigationController: UINavigationController) {

    // start method has completed presentation of Sensibill UI on host.
}

Should show error view when fatal error occurs

Some errors that the SDK will encounter are deemed to be “unrecoverable”, such as having no network connection. When one of these errors occur, the SDK will automatically swap out its current view with an internal view that contains user-friendly messaging, a retry button, and a close button. The retry button will trigger an internal retry, and the close button will trigger the coordinatorWillTerminateWebUI function.

If you do not wish for this ErrorView to be automatically displayed and instead want to handle it yourself, you can implement the following:

func coordinatorShouldShowFatalErrorView(_ coordinator: SensibillUICoordinator) -> Bool {
    // Handle the error manually here

    return false
}

Will Present Capture

This function is called when a user attempts to launch the capture flow. If the SDK’s attachLocationData option has been enabled then this is an ideal time to request location permissions if you have not already done so previously in your app.

func coordinatorWillPresentCapture(_ coordinator: SensibillUICoordinator) {

    // Example: Request Location Permission.
}

Will Restart Sensibill UI

This function is called when the SDK encounters an internal error it believes it can recover from. The SDK attempts an internal restart of the module, and will throw a non-recoverable error, attempting to terminate if it fails to restart 5 times.

func coordinatorWillRestartWebUI(_ coordinator: SensibillUICoordinator) {
    // Debug log
}

Encountered Internal Error

This function is called when an internal error occurs while trying to start the SensibillUICoordinator. It can be used for debugging purposes.

func coordinator(_ coordinator: SensibillUICoordinator, encountered error: Error) {

    // Log error information for internal debugging.
}
Info

Please make sure you have correctly started the SensibillSDK before launching the new Spend Manager. Follow Initializing the SDK documentation as reference.

Warning

If you are seeing errors similar to “Unresolved reference: WebUiActivity”, please reference Installation Options and make sure you are using version >= 2021.X.X.

There are three ways to launch the Spend Manager UI. They range from the simplest integrations with the least customization available, to more complex integrations with much more customization available. See below for instructions on each.

Basic Integration

The most basic way to integrate the new Spend Manager is to directly start the WebUiActivity. This will use the default configurations, navigating directly to the dashboard.

Example

Kotlin

startActivity(Intent(this, WebUiActivity::class.java))

Java

startActivity(new Intent(this, WebUiActivity.class));

Reference WebUiActivity

Subclass WebUiActivity Integration

Alternately, you can subclass the WebUiActivity into your own activity which will allow you to override several listeners. This gives you more customization over how the activity and fragment should react to specific events.

When you subclass the WebUiActivity, you do not need to override anything for it to work. It will use all the defaults provided by the WebUiActivity and the WebUiFragment. If you are using your own layout, then its important to override the callSetContentView and webContainerId so that the WebUiActivity knows where to place the WebUiFragment.

Example

Kotlin

class SubclassKotlinActivity : WebUiActivity(), WebUiFragment.Listener {
    private lateinit var binding: ActivitySubclassKotlinBinding

    override fun callSetContentView() {
        binding = ActivitySubclassKotlinBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }

    override val webContainerId: Int
        get() =  R.id.web_container
}

Java

public class SubclassJavaActivity extends WebUiActivity implements WebUiFragment.Listener {
    private ActivitySubclassJavaBinding binding;

    @Override
    protected void callSetContentView() {
        binding = ActivitySubclassJavaBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
    }

    @Override
    protected int getWebContainerId() {
        return R.id.web_container;
    }
}

Reference WebUiActivity
callSetContentView()
getWebContainerId

Direct Fragment Integration

Integration can also be done using just the WebUiFragment. Create an instance of the fragment and add it to the fragment manager within your activity.

When using WebUiFragment directly, please ensure to properly implement the Listener methods. Also, please use, or see WebUiNetworkErrorFragment as reference for what to display when Listener.onDisplayNetworkError is called.

Example

Kotlin

class FragmentKotlinActivity : AppCompatActivity(), WebUiFragment.Listener {
    private lateinit var binding: ActivityFragmentKotlinBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityFragmentKotlinBinding.inflate(layoutInflater)
        setContentView(binding.root)

        if(savedInstanceState == null) {
            supportFragmentManager.commit {
                replace(binding.fragmentContainer.id, WebUiFragment(), "FRAGMENT_TAG")
            }
        }
    }

    override fun onDisplayNetworkError(networkNotAvailable: Boolean) {
        supportFragmentManager.commit {
            replace(binding.fragmentContainer.id, WebUiNetworkErrorFragment(), "NETWORK_ERROR_FRAGMENT_TAG")
        }
    }

    override fun onRequestFinish(reason: UiFinishReason) {
        finish()
    }
}

Java

public class FragmentJavaActivity extends AppCompatActivity implements WebUiFragment.Listener {
    private ActivityFragmentJavaBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityFragmentJavaBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        if(savedInstanceState == null) {
            getSupportFragmentManager()
                    .beginTransaction()
                    .replace(
                            binding.fragmentContainer.getId(),
                            new WebUiFragment(),
                            "FRAGMENT_TAG")
                    .commit();
        }
    }

    @Override
    public void onDisplayNetworkError(boolean networkNotAvailable) {
        getSupportFragmentManager()
                .beginTransaction()
                .replace(
                        binding.fragmentContainer.getId(),  
                        new WebUiNetworkErrorFragment(),
                        "NETWORK_ERROR_FRAGMENT_TAG")
                .commit();
    }

    @Override
    public void onRequestFinish(@NotNull UiFinishReason uiFinishReason) {
        finish();
    }
}

Reference WebUiActivity
onDisplayNetworkError(networkNotAvailable: Boolean)
onRequestFinish(reason: UiFinishReason)

Demo

For a working example of each implementation, please refer to our demo application sample-spend-manager-android .