Android

Receipt Capture

This document covers how to use both the Capture Flow , as well as the Capture With Metadata Flow use cases.

For additional information on the capture use cases, please see the Use Cases section of the Use Receipt Capture page

Capture Flow Usage

  • To launch the capture flow, use the CaptureFlowCoordinator class.
  • The Capture Flow includes capturing receipt images, uploading the resulting images, and notifying a provided listener of flow progress.
  • The CaptureFlowCoordinator can be used from any AppCompatActivity.
  • For additional reference, please see the CaptureFlowCoordinator Reference Documentation.
Info

- Please ensure you have the required dependencies imported via Gradle. See the installation page for more details.
- Before starting the Capture Flow, the Sensibill SDK must be started .

1: You must create a CaptureFlowCoordinator in the Activity from which you wish to launch the Capture Flow.

private val captureFlow = CaptureFlowCoordinator(this)
private CaptureFlowCoordinator captureFlow = new CaptureFlowCoordinator(this);

Documentation Links:

CaptureFlowCoordinator

2: You must create a CaptureFlowListener (or, alternatively you can have your host Activity implement CaptureFlowCoordinator.CaptureFlowListener)

private val captureFlowListener: CaptureFlowCoordinator.CaptureFlowListener = object : CaptureFlowCoordinator.CaptureFlowListener {
    override fun onCaptureFlowUpdate(newState: CaptureFlowState, externalAccountTransactionId: String?) {
        when (newState) {
            is CaptureFlowState.ImagesCaptured -> { 
                // Handle the state that images have just been captured
                val imagesPendingUpload: List<PendingUploadReceiptData> = newState.imagesPendingUpload
            }
            is CaptureFlowState.FLOW_CANCELLED -> { /* Handle the user cancelling the flow */ }
            is CaptureFlowState.Error -> {
                // Handle an error occurring during the flow
                val error = newState.exception
            }
            is CaptureFlowState.Transacting -> {
                // Handle receipt upload transaction updates that will occur after the image has been captured
                // and the receipt is uploading
                val transaction = newState.transaction
                val currentTransactionStatus = transaction.status
            }
        }
    }
}
private CaptureFlowCoordinator.CaptureFlowListener captureFlowListener = new CaptureFlowCoordinator.CaptureFlowListener() {
    @Override
    public void onCaptureFlowUpdate(@NotNull CaptureFlowState newState, @Nullable String externalAccountTransactionId) {
        if (newState instanceof CaptureFlowState.ImagesCaptured) {
            // Handle the state that images have just been captured
            final CaptureFlowState.ImagesCaptured imagesCapturedState = (CaptureFlowState.ImagesCaptured) newState;
            final List<PendingUploadReceiptData> imagesPendingUpload = imagesCapturedState.imagesPendingUpload;
        } else if (newState instanceof CaptureFlowState.FLOW_CANCELLED) {
            // Handle the user cancelling the flow
        } else if (newState instanceof CaptureFlowState.Error) {
            // Handle an error occurring during the flow
            final CaptureFlowState.Error errorState = (CaptureFlowState.Error) newState;
            final Exception error = errorState.getException();
            
        } else if (newState instanceof  CaptureFlowState.Transacting) {
            // Handle receipt upload transaction updates that will occur after the image has been captured
            // and the receipt is uploading
            final CaptureFlowState.Transacting transactingState = ((CaptureFlowState.Transacting) newState); 
            final Transaction transaction = transactingState.getTransaction();
            final Transaction.Status currentTransactionStatus = transaction.getStatus();
            
        }
    }
};

3: Launch the Capture Flow when required by calling CaptureFlowCoordinator.launchCaptureFlow().

captureFlow.launchCaptureFlow(captureFlowListener)
captureFlow.launchCaptureFlow(captureFlowListener);

4: Handle results. The operations in step 3 will launch the capture UI, as well as upload any resulting images. Continue to monitor your CaptureFlowListener for progress updates and handle the updates accordingly.

Capture With Metadata Flow Usage

Info

- Please ensure you have the required dependencies imported via Gradle. See the installation page for more details. The Capture Flow With Metadata use case requires “Full SDK Functionality” dependencies.
- Before starting the Capture Flow With Metadata, the Sensibill SDK must be started .

  • To launch the capture with metadata flow, launch CaptureWithMetadataFlowActivity
  • The Capture With Metadata Flow includes capturing receipt images, uploading the resulting images and showing an Edit Receipt Metadata screen to the user as the upload and processing progresses
  • For additional reference, please see the CaptureWithMetadataFlowActivity Reference Documentation.

1: Launch the CaptureWithMetadataFlowActivity

val intent = Intent(this, CaptureWithMetadataFlowActivity::class.java)

// If you wish to, override the default capture config
val captureConfig = CaptureConfig.defaultConfig.copy(enableLongCapture = false)
intent.putExtra(CaptureWithMetadataFlowActivity.ARG_CAPTURE_CONFIG, captureConfig)

// If the receipt should be attached to some external transaction.  Please note that an external account transaction 
// id can only be provided if `CaptureConfig.maxImages` is set to 1
val externalAccountTransactionId = "someId"
intent.putExtra(CaptureWithMetadataFlowActivity.ARG_EXTERNAL_ACCOUNT_TRANSACTION_ID, externalAccountTransactionId)

// If the result of the capture action doesn't need to be checked, or you're observing transaction 
// updates separately via your own `TransactionDataObserver`
startActivity(intent)

// If you wish to know upon the activity finishing if images were successfully captured
startActivityForResult(intent, MY_REQUEST_CODE)
final Intent intent = new Intent(this, CaptureWithMetadataFlowActivity.class);

// If you wish to override the default capture config
final CaptureConfig captureConfig = new CaptureConfig(
        true,
        FlashMode.FLASH_MODE_OFF,
        true,
        true,
        false,
        true,
        true,
        false,
        1,
        false,
        true,
        true,
        true,
        true,
        true
);
intent.putExtra(CaptureWithMetadataFlowActivity.ARG_CAPTURE_CONFIG, captureConfig);

// If the receipt should be attached to some external transaction.  Please note that an external account transaction 
// id can only be provided if `CaptureConfig.maxImages` is set to 1
final String externalAccountTransactionId = "someId";
intent.putExtra(CaptureWithMetadataFlowActivity.ARG_EXTERNAL_ACCOUNT_TRANSACTION_ID, externalAccountTransactionId);

// If the result of the capture action doesn't need to be checked, or you're observing transaction
// updates separately via your own `TransactionDataObserver`
startActivity(intent);

// If you wish to know upon the activity finishing if images were successfully captured
startActivityForResult(intent, MY_REQUEST_CODE);

2: If desired, handle the result

// Only required if using `startActivityForResult`
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    when (requestCode) {
        MY_REQUEST_CODE -> {
            if (resultCode == Activity.RESULT_OK) {
                // Handle receipts being successfully captured.
                // At this point, the receipts could be uploading, uploaded and processing, processed, 
                // or failed to process.
                // In order to get more granular information, you should implement your own `TransactionDataObserver`
            } else {
                // Handle receipt capture failure.  This most likely means that the user cancelled the capture flow.
            }
        }
        else -> super.onActivityResult(requestCode, resultCode, data)
    }
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    if (requestCode == MY_REQUEST_CODE) {
        if (resultCode == Activity.RESULT_OK) {
            // Handle receipts being successfully captured.
            // At this point, the receipts could be uploading, uploaded and processing, processed,
            // or failed to process.
            // In order to get more granular information, you should implement your own `TransactionDataObserver`
        } else {
            // Handle receipt capture failure.  This most likely means that the user cancelled the capture flow.
        }
    } else {
        super.onActivityResult(requestCode, resultCode, data);
    }
}

Documentation Links:

TransactionDataObserver

Customization

Functionality

Additional arguments can be provided to the CaptureFlowCoordinator.launchCaptureFlow() call to customize the behaviour of the Capture Flow.

  • config: CaptureConfig: A CaptureConfig object can be provided to customize the behaviour of capture itself. Please see the Reference Documentation for details on the configurable properties of CaptureConfig.
    • In order to more easily be compliant with the above, if your project is in Kotlin it is reccommended to create a custom CaptureConfig using CaptureConfig.defaultConfig.copy() and providing the customizations required to the copy method.
  • externalAccountTransactionId: String?: If the receipt that is about to be captured should be linked with an external transaction (eg. banking transaction), that transaction’s ID can be provided here.
    • NOTE: If providing an externalAccountTransactionId, CaptureConfig.maxImages msut be set to 1. If this rule is ignored, the capture flow will throw an exception.

Branding

In addition to the branding customization available at the Customize Branding page, there is an additional set of colours that is used by the Sensibill Capture Standalone module that can be customized as well.
In order to customize these colours, please create an additional style element with the name New.Theme.Sensibill.CaptureStandalone and parent Base.New.Theme.Sensibill.CaptureStandalone. The available colours to customize will be shown in the example below.

<style name="New.Theme.Sensibill.CaptureStandalone" parent="Base.New.Theme.Sensibill.CaptureStandalone">
    <item name="sb__colourCaptureBackground">#000000 by default</item>
    <item name="sb__colourOnCaptureBackground">#FFFFFF by default</item>
    <item name="sb__colourOnCaptureBackgroundDisabled">#99FFFFFF by default</item>
</style>

Additional branding and UI customization is available by request.

Capture Tips

The “Capture tips” is a toggleable feature, as seen at Feature Swtiching page. Enabled by default, the tips activity displays a series of tips on how best to use the capture process. These tips can be modified as desired.

Replace Tip Data

To modify the existing tips, set the companion field CaptureStandaloneActivity.tipsConfigCreator anytime before the capture flow is started to change what is displayed.

// The returned `ArrayList` from the tips config creator defines what set of tips will be shown in the tips activity
CaptureStandaloneActivity.tipsConfigCreator = { currentCaptureConfig ->
    arrayListOf(
        // Create a `CaptureTipData` for each tip you want to display
        CaptureTipData(
            tipIcon = R.drawable.icon_tip1,
            tipTitle = R.string.title_tip1,
            tipDescription = R.string.description_tip1
        ),
        CaptureTipData(
            tipIcon = R.drawable.icon_tip2,
            tipTitle = R.string.title_tip2,
            tipDescription = R.string.description_tip2
        )
    ).apply {
        // If you need to conditionally include tips based on what the current config capture is running with,
        // a `CaptureConfig` is passed in representing the current configuration of the instance of the capture
        // flow that is about to launch capture tips.
        if (currentCaptureConfig.enableLongCapture) {
            add(
                CaptureTipData(
                    tipIcon = R.drawable.icon_tip3,
                    tipTitle = R.string.title_tip3,
                    tipDescription = R.string.description_tip3
                )
            )
        }
    }
}
// The returned `ArrayList` from the tips config creator defines what set of tips will be shown in the tips activity
CaptureStandaloneActivity.Companion.setTipsConfigCreator(new Function1<CaptureConfig, ArrayList<CaptureTipData>>() {
    @Override
    public ArrayList<CaptureTipData> invoke(CaptureConfig currentCaptureConfig) {
        ArrayList<CaptureTipData> tips = new ArrayList<>();
        // Create a `CaptureTipData` for each tip you want to display
        tips.add(new CaptureTipData(R.drawable.icon_tip1, R.drawable.gradiant_tip, R.string.title_tip1, R.string.description_tip1));
        tips.add(new CaptureTipData(R.drawable.icon_tip2, R.drawable.gradiant_tip, R.string.title_tip2, R.string.description_tip2));

        // If you need to conditionally include tips based on what the current config capture is running with,
        // a `CaptureConfig` is passed in representing the current configuration of the instance of the capture
        // flow that is about to launch capture tips.
        if(currentCaptureConfig.getEnableLongCapture()) {
            tips.add(new CaptureTipData(R.drawable.icon_tip3, R.drawable.gradiant_tip, R.string.title_tip3, R.string.description_tip3));
        }

        return tips;
    }
});

Edit Tip Data

If you just want to replace a single tip or just a few properties of one, you can also do that by updating the CaptureStandaloneActivity.tipsConfigCreator as such.

// The capture configuration you use in your application
val myCaptureConfig = CaptureConfig()

// Generate the default tips
val tips = CaptureStandaloneActivity.tipsConfigCreator(myCaptureConfig)

// Modify the tip you want to change and replace it in the list
val updatedTip = tips[1].copy(tipIcon = R.drawable.my_tip_icon, tipTitle = R.string.my_tip_title)
tips.removeAt(1)
tips.add(1, updatedTip)

// Set the creator to use your custom list
CaptureStandaloneActivity.tipsConfigCreator = { tips }
// The capture configuration you use in your application
CaptureConfig myCaptureConfig = new CaptureConfig();

// Generate the default tips
ArrayList<CaptureTipData> tips = CaptureStandaloneActivity.Companion.getTipsConfigCreator().invoke(myCaptureConfig);

// Modify the tip you want to change and replace it in the list
CaptureTipData updatedTip = tips.get(1).copy(R.drawable.icon_tip1, R.drawable.gradiant_tip, R.string.title_tip1, R.string.description_tip1);
tips.remove(tips.get(1));
tips.add(1, updatedTip);

// Set the creator to use your custom list
CaptureStandaloneActivity.Companion.setTipsConfigCreator(new Function1<CaptureConfig, ArrayList<CaptureTipData>>() {
    @Override
    public ArrayList<CaptureTipData> invoke(CaptureConfig captureConfig) {
        return tips;
    }
});

Additional References

Sensibill Capture Flow Module Reference
(Receipt Upload) Transaction Reference
Capture Config Model
CaptureStandaloneActivity
CaptureTipData
Feature Swtiching page.