A Quick Way To Build Mobile Check Capture App Using Xamarin.Forms
This blog illustrates the steps to use Xamarin.Forms to develop a Mobile Check Capture app for the iOS and Android platforms.
Join the DZone community and get the full member experience.
Join For FreeDepositing money using a check into your bank account is made easy with Mobile Check Deposit. It has eliminated the need to pay a visit to a branch, making depositing a check as simple as clicking a picture of the check using a mobile device and submitting it.
So, instead of depositing checks at an ATM, with a bank teller, and a drive-through window, the users have the flexibility to add them to their account anytime from anywhere using a Mobile Check Capture App.
A Mobile Check Capture app uses the mobile device's camera to click the pictures of the front and back of a check.
This article describes how to use Xamarin.Forms to develop a Mobile Check Capture app for the iOS and Android platforms.
NuGet Packages
The Process To Develop a Mobile Check Capture App
The functionalities of the app include:
- Capturing images of the front and back of a check using the mobile device's camera.
- Detecting edges of the check.
- Editing the quadrilateral area of the check.
Every section will explain how to create a check capture app for mobile devices. Some of the features of the app are as follows:
- Check photos (front and back) may be taken from the live camera feed.
- Find the check edges.
- Make changes to the check's four corners (quadrilateral area).
Step 1: Build a Xamarin.Forms Project
- Build a new project in Visual Studio 2022 with the help of the Mobile App (Xamarin.Forms) template.
- Install Dynamsoft.DocumentNormalizer.Xamarin.Forms by opening the NuGet Package Manager:
- Configure AndroidManifest.xml for Android Info.plist for iOS.
iOS Info.plist
<key>NSCameraUsageDescription</key>
<string>This app is using the camera</string>
Android AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.documentscanner">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="31" />
<application android:label="DocumentScanner.Android" android:theme="@style/MainTheme"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
Step 2: Start Dynamsoft Document Normalizer
- Request for a trial license of Dynamsoft Document Normalizer.
- Write the code for the specific platform to start up Dynamsoft Document Normalizer.
iOS AppDelegate.cs
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
App.ScreenWidth = UIScreen.MainScreen.Bounds.Width;
App.ScreenHeight = UIScreen.MainScreen.Bounds.Height;
LoadApplication(new App(new DCVCameraEnhancer(), new DCVDocumentNormalizer(), new DCVLicenseManager()));
return base.FinishedLaunching(app, options);
Android MainActivity.cs
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
var width = Resources.DisplayMetrics.WidthPixels;
var height = Resources.DisplayMetrics.HeightPixels;
var density = Resources.DisplayMetrics.Density;
App.ScreenWidth = (width - 0.5f) / density;
App.ScreenHeight = (height - 0.5f) / density;
LoadApplication(new App(new DCVCameraEnhancer(this), new DCVDocumentNormalizer(), new DCVLicenseManager(this)));
}
Now, the instances of
ICameraEnhancer
,IDocumentNormalizer
, andILicenseManager
need to be passed to the constructor of App inApp.xaml.cs
.
public static ICameraEnhancer dce;
public static IDocumentNormalizer ddn;
public static double ScreenWidth;
public static double ScreenHeight;
public App(ICameraEnhancer enhancer, IDocumentNormalizer normalizer, ILicenseManager manager)
{
InitializeComponent();
dce = enhancer;
ddn = normalizer;
MainPage = new NavigationPage(new MainPage(manager));
}
Set the license in
MainPage.xaml.cs
and then verify it.
public partial class MainPage : ContentPage, ILicenseVerificationListener
{
private ILicenseManager licenseManager;
private bool isLicenseValid = true;
public MainPage(ILicenseManager licenseManager)
{
InitializeComponent();
this.licenseManager = licenseManager;
licenseManager.InitLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==", this);
}
public void LicenseVerificationCallback (bool isSuccess, string msg)
{
if (!isSuccess)
{
Device.BeginInvokeOnMainThread(async () => {
isLicenseValid = false;
await DisplayAlert("Error", msg, "OK");
});
}
}
}
Background threads invoke the LicenseVerificationCallback()
method. To obtain the most recent UI, use Device.BeginInvokeOnMainThread()
.
Step 3: Design the Main Page UI
On the main page, three Xamarin.Forms widgets are used:
- Image: Display a static PNG image built as an embedded resource.
- Label: Display some description text.
- Button: Navigate to the check information page.
The process to load an embedded resource image in Xamarin.Forms:
- You can embed a picture into your project by adding an image file and setting its Build Action to Embedded resource.
- To load the embedded resource images, build an
ImageResourceExtension
class.
// You exclude the 'Extension' suffix when using in Xaml markup
[Preserve(AllMembers = true)]
[ContentProperty(nameof(Source))]
public class ImageResourceExtension : IMarkupExtension
{
public string Source { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (Source == null)
return null;
var imageSource = ImageSource.FromResource(Source, typeof(ImageResourceExtension).GetTypeInfo().Assembly);
return imageSource;
}
}
Import the namespace of local in a XAML file, and then load a PNG file as follows:
XML<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:DocumentScanner;assembly=DocumentScanner" x:Class="DocumentScanner.MainPage" BackgroundColor="Transparent" Title="CheckCapture"> <StackLayout Margin="20,20,20,20" > <Image Source="{local:ImageResource DocumentScanner.icon-cover.png}"/> </StackLayout> </ContentPage>
The local:ImageResource
can be used to set an image source for a button as well:
<Button x:Name="customRenderer" Text="Deposit Check" TextColor="White" HorizontalOptions="Center" Clicked="OnCustomRendererButtonClicked" BackgroundColor="Orange" ImageSource="{local:ImageResource DocumentScanner.icon-capture.png}" HeightRequest="50" WidthRequest="200"/>
Step 4: Design the Check Information Page UI
You'll find the following widgets on the check info page:
TitleView
: A customized title view with text labeling and an upload button.
<NavigationPage.TitleView>
<StackLayout Orientation="Horizontal" Margin="0,0,10,0">
<Label Text="Check" TextColor="white" FontAttributes="Bold" VerticalOptions="Center" HorizontalOptions="StartAndExpand"/>
<Button
x:Name="upload"
Clicked="OnUploadClicked"
BackgroundColor="transparent" HeightRequest="50" WidthRequest="50" ImageSource="{local:ImageResource DocumentScanner.icon-upload.png}" VerticalOptions="Center">
</Button>
</StackLayout>
</NavigationPage.TitleView>
Editor
: Enter the check amount.
<StackLayout Orientation="Horizontal" Margin="20,0,20,20">
<Label Text="Amount: $" FontAttributes="Bold"
VerticalOptions="Center" />
<Editor x:Name="amount" HorizontalOptions="FillAndExpand" Placeholder="" VerticalOptions="Center"/>
</StackLayout>
Frame
: Build a layout that has rounded corners.
<Frame CornerRadius="10" Margin="20,20,20,20">
<StackLayout HeightRequest="250" WidthRequest="400" BackgroundColor="White" >
</StackLayout>
</Frame>
Image
: The check image captured will be displayed. To access the editor, click the image.
<Image x:Name="front_image" Aspect="AspectFit">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="FrontImageTapped" />
</Image.GestureRecognizers>
</Image>
Label
: Display the description text.Button
: Launch the check capture page.
You can use MessagingCenter
to update the image source when you capture or edit image quadrilaterals from another content page.
Subscribe to the ImageData
message on the check info page. The check capture page is- CustomRendererPage
.
MessagingCenter.Subscribe<CustomRendererPage, InfoData>(this, "ImageData", (sender, arg) =>
{
App.ddn.InitRuntimeSettings(Templates.color);
NormalizedImageResult normalizedImage = App.ddn.Normalize(arg.imageData, arg.quad);
if (isFront)
{
_frontData = arg;
front_image.Source = normalizedImage.image.ToImageSource();
if (Device.RuntimePlatform == Device.iOS)
{
front_image.RotateTo(normalizedImage.image.orientation + 180);
}
else
{
front_image.RotateTo(270);
}
}
else
{
_backData = arg;
back_image.Source = normalizedImage.image.ToImageSource();
if (Device.RuntimePlatform == Device.iOS)
{
back_image.RotateTo(normalizedImage.image.orientation + 180);
}
else
{
back_image.RotateTo(270);
}
}
});
Now, send the image data to the info page of the check in the CustomRendererPage
:
public void DetectResultCallback(int id, ImageData imageData, DetectedQuadResult[] quadResults)
{
if (imageData != null && quadResults != null)
{
Device.BeginInvokeOnMainThread(async () => {
ImageData data = new ImageData();
data.imageSource = imageData.imageSource;
data.bytes = new List<byte>(imageData.bytes);
data.width = imageData.width;
data.height = imageData.height;
data.stride = imageData.stride;
data.format = imageData.format;
data.orientation = imageData.orientation;
InfoData info = new InfoData();
info.imageData = data;
info.quad = quadResults[0].Location;
MessagingCenter.Send(this, "ImageData", info);
await Navigation.PopAsync();
});
}
}
Step 5: To Capture the Images of a Check, Both, Front and Back
The DCVCameraView
widget needs to be added to display the camera stream. The CustomRendererPage
will be leveraged to identify the document edges from the camera stream in real-time.
The Label and Button are rotated 90 degrees as the users need to hold the screen in landscape mode to get a better capture experience.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:dynamsoft = "clr-namespace:DDNXamarin;assembly=DDN-Xamarin"
xmlns:local="clr-namespace:DocumentScanner;assembly=DocumentScanner"
x:Class="DocumentScanner.CustomRendererPage"
Title="Scan Check">
<ContentPage.Content>
<AbsoluteLayout>
<dynamsoft:DCVCameraView AbsoluteLayout.LayoutBounds="0,0,1,1" AbsoluteLayout.LayoutFlags="All" x:Name="preview">
</dynamsoft:DCVCameraView>
<Label Text="Please use landscape mode" Rotation="90" TextColor="White" AbsoluteLayout.LayoutBounds="0.7,0.5,300,300" AbsoluteLayout.LayoutFlags="PositionProportional"/>
<Button x:Name="capture"
AbsoluteLayout.LayoutBounds="0.5,0.95,80,80" AbsoluteLayout.LayoutFlags="PositionProportional"
Clicked="OnButtonClicked" ImageSource="{local:ImageResource DocumentScanner.icon-capture.png}" Rotation="90" BackgroundColor="Orange" BorderRadius="40"
>
</Button>
</AbsoluteLayout>
</ContentPage.Content>
</ContentPage>
When the document edges are found, the button triggers DetectResultCallback
.
void OnButtonClicked(object sender, EventArgs e)
{
App.ddn.EnableReturnImageOnNextCallback();
}
Step 6: Edit the Check’s Quadrilateral Area
The editing page has a DCVImageEditorView
, which allows the four sides or corners of the document to be changed and saved.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:dynamsoft = "clr-namespace:DDNXamarin;assembly=DDN-Xamarin"
xmlns:local="clr-namespace:DocumentScanner;assembly=DocumentScanner"
x:Class="DocumentScanner.QuadEditorPage">
<NavigationPage.TitleView>
<StackLayout Orientation="Horizontal" Margin="0,0,10,0">
<Label Text="Edit and Save Quad" TextColor="white" FontAttributes="Bold" VerticalOptions="Center" HorizontalOptions="StartAndExpand"/>
<Button
x:Name="normalize"
Clicked="OnNormalizeClicked"
BackgroundColor="transparent" HeightRequest="50" WidthRequest="50" ImageSource="{local:ImageResource DocumentScanner.icon-save.png}" VerticalOptions="Center">
</Button>
</StackLayout>
</NavigationPage.TitleView>
<ContentPage.Content>
<AbsoluteLayout>
<dynamsoft:DCVImageEditorView AbsoluteLayout.LayoutBounds="0,0,1,1" AbsoluteLayout.LayoutFlags="All"
x:Name="imageEditor">
</dynamsoft:DCVImageEditorView>
</AbsoluteLayout>
</ContentPage.Content>
</ContentPage>
On the check info page, send the image and the quadrilateral that has been detected to the editor page:
private void BackImageTapped(object sender, EventArgs e)
{
if (_backData != null) {
DetectedQuadResult result = new DetectedQuadResult();
result.Location = _backData.quad;
Navigation.PushAsync(new QuadEditorPage(_backData.imageData, new DetectedQuadResult[] {result}));
}
}
Get the new quadrilateral from the editor page and send it back to the info page through MessagingCenter
:
async void OnNormalizeClicked(object sender, EventArgs e)
{
try
{
var quad = imageEditor.getSelectedQuadResult();
if (quad != null)
{
InfoData data = new InfoData();
data.imageData = this.data;
data.quad = quad;
MessagingCenter.Send(this, "ImageData", data);
await Navigation.PopAsync();
}
}
catch (Exception exception)
{
Device.BeginInvokeOnMainThread(async () => {
await DisplayAlert("Error", exception.ToString(), "OK");
});
}
}
Opinions expressed by DZone contributors are their own.
Comments