Android
Document 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 anyAppCompatActivity
. - For additional reference, please see the CaptureFlowCoordinator Reference Documentation.
Info
- Please ensure you have the required dependencies imported via Gradle. See the Installation Options
page for more details.
- Before starting the Capture Flow, the Sensibill SDK must be started. See Android Lifecycle
page for more information.
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:
2: You must create a CaptureFlowListener
(or, alternatively you can have your host Activity
implement CaptureFlowCoordinator.CaptureFlowListener
)Please note: that the Transacting
state only applies if you are configured to use the v1 (receipts) endpoints, and DocumentUploading
only applies if you are configured to use the v2 (documents) endpoints. Please only include one of these options.
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<PendingUploadItemData> = 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
}
// **ONLY APPLICABLE IF CONFIGURED USING API V1 (RECEIPTS) ENDPOINTS**
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
}
// **ONLY APPLICABLE IF CONFIGURED USING API V2 (DOCUMENTS) ENDPOINTS**
is CaptureFlowState.DocumentUploading -> {
// Handle document upload progress updates that will occur after the image has been captured and the
// Document is uploading
val update = newState.update
val status = update.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<PendingUploadItemData> 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();
// **ONLY APPLICABLE IF CONFIGURED USING API V1 (RECEIPTS) ENDPOINTS**
} 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();
// **ONLY APPLICABLE IF CONFIGURED USING API V2 (DOCUMENTS) ENDPOINTS**
} else if (newState instanceof CaptureFlowState.DocumentUploading) {
// Handle document upload progress updates that will occur after the image has been captured and the
// Document is uploading
final DocumentUploadUpdateData update = ((CaptureFlowState.DocumentUploading) newState).getUpdate();
final DocumentStatus status = update.getStatus();
}
}
};
Documentation Links:
CaptureFlowListenerCaptureFlowStateTransactionTransaction Status
3: Launch the Capture Flow when required by calling CaptureFlowCoordinator.launchCaptureFlow()
.Please note: that the DocumentType
configuration is only valid if configured to use the v2 (documents) endpoints. If this configuration is provided when NOT configured to use v2 endpoints, an exception will be thrown.
captureFlow.launchCaptureFlow(
// (Required) Your capture flow listener
listener = captureFlowListener,
// (Optional, default null) captureConfig. If none provided, will default to the default config for the
// provided document type
config = null,
// (Optional, default null) external account transaction id to add an external account
// transaction id to a captured document
externalAccountTransactionId = "myExtTxnId",
// (Optional, default RECEIPT) ONLY APPLICABLE IF USING V2 (DOCUMENTS) API ENDPOINTS
// The type of document to capture, receipt or invoice
documentType = DocumentType.RECEIPT
)
captureFlow.launchCaptureFlow(
// (Required) Your capture flow listener
captureFlowListener,
// (Optional, default null) captureConfig. If none provided, will default to the default config for the
// provided document type
null,
// (Optional, default null) external account transaction id to add an external account
// transaction id to a captured document
"myExtTxnId",
// (Optional, default RECEIPT) ONLY APPLICABLE IF USING V2 (DOCUMENTS) API ENDPOINTS
// The type of document to capture, receipt or invoice
DocumentType.RECEIPT
);
Documentation Links:
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 Options
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. See Android Lifecycle
page for more information.
TBD
Customization
Functionality
Additional arguments can be provided to the CaptureFlowCoordinator.launchCaptureFlow()
call to customize the behaviour of the Capture Flow.
config: CaptureConfig
: ACaptureConfig
object can be provided to customize the behaviour of capture itself. Please see the Reference Documentation for details on the configurable properties ofCaptureConfig
.- In order to more easily be compliant with the above, if your project is in Kotlin it is reccommended to create a custom
CaptureConfig
usingCaptureConfig.defaultConfig.copy()
and providing the customizations required to thecopy
method.
- In order to more easily be compliant with the above, if your project is in Kotlin it is reccommended to create a custom
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 to1
. If this rule is ignored, the capture flow will throw an exception.
- NOTE: If providing an
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 ReferenceCapture Config ModelCaptureStandaloneActivityCaptureTipDataFeature Swtiching page