# Android Integration
# Getting Started
# Resolving the SDK using gradle:
The anybill SDK for android is hosted in a maven repository. To resolve the sdk using gradle, follow these steps:
- Add this to your project's 'build.gradle'. You can find your credentials in the provided integration documents.
allprojects {
repositories {
mavenCentral()
maven {
url "https://anybill.jfrog.io/artifactory/anybill_android_sdk"
credentials {
username = {username}
password = {password}
}
}
}
}
- Add the desired anybill modules to your module's 'build.gradle'. The following modules are required for the basic usage of the SDK: core, auth, bill, log, category
dependencies {
implementation 'de.anybill.anybill_android_sdk:core:{latest version}'
implementation 'de.anybill.anybill_android_sdk:log:{latest version}'
implementation 'de.anybill.anybill_android_sdk:auth:{latest version}'
implementation 'de.anybill.anybill_android_sdk:bill:{latest version}'
implementation 'de.anybill.anybill_android_sdk:category:{latest version}'
}
# Setting the client Id
Within the provided integration documents you are going find a client ID.
To set the Client ID in your app, add the ID as meta data value anybill_client_id
in your app's manifest file. This is going to allow us to hook all of your API activity to your Client Id which can be used for analytics or support purposes later on.
<application
...
>
...
<meta-data android:name="anybill_client_id" android:value="{your_client_id}"/>
...
</application>
# Change the api mode to staging
For developing purposes you can change the api environment to Staging by adding the Staging url (https://app.stg.anybill.de/) as value for the key anybill_base_url
in your AndroidManifest.xml:
<application
...
>
...
<meta-data android:name="anybill_base_url" android:value="https://app.stg.anybill.de/"/>
...
</application>
# Usage of the SDK
# Error Handling
The anybill sdk uses a custom error handling model. All methods return a type of the sealed class ApiResult
including the return object on success and/or information about the error which occurred.
Detailed description of the possible error codes can be found in the corresponding documentation of the methods.
sealed class ApiResult<out T> {
/**
* Success\
* Used when Api Call and serialization was successful\
*
* @param T Type [T] of the object which should be returned if Api call and JSON Serialization was successful
* @property value Object of type [T] holding the serialized result of the api call
*/
data class Success<out T>(val value: T) : ApiResult<T>()
/**
* Generic error\
* Used if a generic error occurs during execution of the api call or serialization of the received data\
* Possible GenericError's of each operation are listed in the method documentation
*
* @property code Nullable [Int] with the Error Code of the exception
* @property error Nullable [ErrorResponse] object with further exception information
*/
data class GenericError(val code: Int? = null, val error: ErrorResponse? = null) : ApiResult<Nothing>()
/**
* Empty success\
* Used if the execution of the api call and the serialization of the result was successful without a need of a result object (e.g. token operations)\
*
*/
object EmptySuccess : ApiResult<Nothing>()
/**
* Network error\
* Used if a network error occurs during execution of the api call (e.g. timeout, no network)
*/
object NetworkError : ApiResult<Nothing>()
}
Example usage of the error model based on the user info method of the AuthProvider.
fun getUserInfo() {
viewModelScope.launch {
when (val userInfoTask = AuthProvider.getUserInfo()) {
is NetworkError -> //Error Handling
is GenericError -> {
when(userInfoTask.code){
NO_USER -> //Error Handling
NOT_SYNCABLE -> //Error Handling
SERVER_ERROR -> //Error Handling
REFRESH_ERROR -> //Error Handling
}
}
is Success -> {
userInfoTask.value //Further implementation
}
}
}
}
# Push Notification
The anybill sdk also provides the possibility to receive push notification from anybill's firebase project. Push notifications are used for reminders of users' warranties and certain login verifications. The usage of push notification is optional for the core modules of the anybill sdk. Features which require the usage of push notification are marked.
How to implement push notifications for the anybill sdk:
# Without an existing Firebase project
If the app is in background, push notification are handled by google services and displayed automatically. If the app is in foreground push notification are caught by the FirebaseMessagingService
and have to be displayed manually.
The anybill sdk provides an interface which can be implemented to your own notification service to display notifications:
- Create a notification handler:
class YourNotificationHandler(private val context: Context) : AnybillMessagingHandler {
override fun onAnybillMessageReceived(remoteMessage: RemoteMessage) {
// Display Notification
}
}
- Initialize anybill notification service with your messaging handler in the
onCreate()
of your application.
class YourApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize the anybill Firebase project
AnybillFirebaseInitializer.initFirebaseProject(this)
// Set your notification handler
AnybillMessagingService.setMessageHandler(YourNotificationHandler(this))
}
}
Notifications can be customized or extended with Intents to navigate different views.
# With an existing Firebase project
If your app already uses a Firebase project, the anybill Firebase project can be initialized as a secondary Firebase project.
To ensure that the anybill project is not initialized as the default project, call the initializing method after your default project was initialized. The default Firebase project gets initialized by the FirebaseInitProvider
. To enable a manual initialization of the Firebase project, disable the provider by adding this to your app's manifest:
<application>
...
<provider
android:name="com.google.firebase.provider.FirebaseInitProvider"
android:authorities="${applicationId}.firebaseinitprovider"
tools:node="remove"
/>
...
</application>
The default project can now be initialized using FirebaseApp.initializeApp()
in the onCreate()
method of your application:
class YourApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize your default Firebase project
FirebaseApp.initializeApp(this)
// Initialize the anybill Firebase project
AnybillFirebaseInitializer.initFirebaseProject(this)
}
}
Implementation of notifications for a secondary project differs if the default project also uses Firebase Messaging:
# Default project does not use Firebase Messaging
If the default project does not use Firebase Messaging, displaying notification works as described in the section Without an existing Firebase project
. To make sure that the anybill project is not initialized as the default project, initialize your Firebase project before initializing the anybill Firebase project (see step 2).
The anybill sdk provides an interface which can be implemented to your own notification service to display notifications:
- Create an notification handler:
class YourNotificationHandler(private val context: Context) : AnybillMessagingHandler {
override fun onAnybillMessageReceived(message: RemoteMessage) {
// Display Notification
}
}
- Initialize anybill notification service with your messaging handler in the
onCreate()
of your application.
class YourApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize your default Firebase project
FirebaseApp.initializeApp(this)
// Initialize the anybill Firebase project
AnybillFirebaseInitializer.initFirebaseProject(this)
// Set your notification handler
AnybillMessagingService.setMessageHandler(YourNotificationHandler(this))
}
}
# Default project uses Firebase Messaging
Firebase only allows the usage of one FirebaseMessagingService
. To allow the anybill sdk to execute operation on specific notifications the notification you receive in your notification service have to be given to the anybill sdk.
- Sample code of your notification service:
class YourNotificationService : FirebaseMessagingService(), AnybillMessagingHandler {
//Initialize an instance of AnybillMessagingService
private val anybillMessagingService by lazy {
AnybillMessagingService.getInstance(this)
}
//In the onMessageReceived of your FirebaseMessagingService:
//Check if a notification should be handled by the anybill sdk by calling isAnybillMessage()
//Let anybill messaging service handle the notification
override fun onMessageReceived(message: RemoteMessage) {
if (message.isAnybillMessage()) {
anybillMessagingService.onAnybillMessageReceived(message)
} else {
//Display Notification
}
super.onMessageReceived(message)
}
//Firebase Messaging Tokens mostly get reset for both projects simultaneously.
//To reset the notification token of the logged in anybill user, call onNewAnybillToken in your onNewToken method
override fun onNewToken(token: String) {
anybillMessagingService.onNewAnybillToken()
super.onNewToken(token)
}
//Display the received anybill notification after it was handled by the anybill sdk.
override fun onAnybillMessageReceived(messageBody: RemoteMessage) {
//Display Notification
}
}
# Default handling of push notifications
As just demonstrated, notifications are handled inside the onAnybillMessageReceived
method. The simplest way of handling the notifications is to just dislpay them
as local android notifications. However the SDK also offers the possibility to display them on UI components of the SDK as a snackbar. Additionally some special notifications trigger
specific actions when handled by the SDK such as automatically updating the list of bills when a notifications arrives, stating that a new receipt has been added to the logged in account. To enable the SDK internal handling of notifications, the method handleAnybillNotification
of the class AnybillMessagingService
can be called like so:
override fun onAnybillMessageReceived(remoteMessage: RemoteMessage) {
anybillNotificationHandler.handleAnybillNotification(remoteMessage)
}
To avoid displaying of multiple snackbars/notifications it is advised to first check if the SDK UI is currently being used and based on the result either let the SDK handle the notification or let the implementing app display the notification.
# AnybillCore
The AnybillCore module contains several constants and functionalities which are shared between all anybill modules. Constants and methods of the Core Module should not be accessed from the app module.
# AnybillAuth
The Auth module contains authentication functions used in the anybill sdk. Most authentication functions can be accessed using the Singleton AuthProvider
. Most functions of the AuthProvider are suspend functions and have to be called in a coroutine scope.
# Login
To login a user you can choose between logging in an anybill user, an anonymous user or use your own authentication token (token user).
Anybill user
Anybill users can be logged in using the loginUser() method of the AuthProvider. It requires valid login information of a registered anybill user (email and password).
fun loginUser(email: String, password: String) {
viewModelScope.launch {
when (val loginCall = AuthProvider.loginUser(email = email, password = password)){
is ApiResult.EmptySuccess -> //Success
is ApiResult.GenericError -> //Error Handling
is ApiResult.NetworkError -> //Error Handling
}
}
}
Anonymous user
By using this functionality the SDK creates an anonymous user account in the background with no email or password. With this authentication method the user can use the anybill services (with certain restrictions) as if he had no user account. The anonymous user account can be converted to a normal anybill account with email and password later on.
fun loginAnonymousUser() {
when (val createAnonymousCall = AuthProvider.createAnonymousUser()){
is ApiResult.EmptySuccess -> //Success
is ApiResult.GenericError -> //Error Handling
is ApiResult.NetworkError -> //Error Handling
}
}
After an App or its data gets deleted, the anonymous account cannot be restored because there is no reference to the old account left.
Linked token user
If your App has an own user account system you can link an anybill user to your user account by using the linked token user:
- Get a token from the anybill Partner API by linking you account system to an anybill id.
- Create an instance of
TokenUser
with the received token-information - Login the TokenUser with the anybill sdk
fun loginTokenUser(tokenUser: TokenUser){
when(val tokenLogin = AuthProvider.loginTokenUser(tokenUser)){
is ApiResult.EmptySuccess -> //Success
is ApiResult.GenericError -> //Error Handling
}
}
When using the token user variant you'll have to check for a failing Refresh Token Call on every API call you invoke. When the error is triggered you'll have to retrieve new authentication information from the anybill Partner API.
when(val userInfoCall = AuthProvider.getUserInfo()){
is ApiResult.Success -> //Success
is ApiResult.GenericError -> {
when (userInfoCall.code) {
AuthErrors.REFRESH_ERROR -> {
// Retrieve new token information from anybill Partner API
}
}
}
is ApiResult.NetworkError -> // Error Handling
}
Further information about the login possibilities can be found on the anybill sdk authentication page..
# Register
To register a new anybill user use the preRegisterUser()
method, which sends a validation code to the given user email. You can validate a register code with the validateRegistrationCode()
method. Use the registerUser()
method to complete the registration process.
By setting autoLogin
to true
the user is going to be automatically logged in after a successful registration.
viewModelScope.launch {
when(val preRegisterCall = anybillAuth.preRegisterUser(email, firstname)){
is EmptySuccess -> //Successfully sent code to email
is GenericError -> //Error Handling
is NetworkError -> //Error Handling
}
}
//Let user type in registration code
viewModelScope.launch{
when(val registerCall = anybillAuth.registerUser(email,password, code)){
is Success -> //Success
is GenericError -> //Error Handling
is NetworkError -> //Error Handling
}
}
# Deprecated: Register
To register a new anybill user use the registerUser()
method. It requires a valid email, a password which fulfills anybill's password policy (at least 8 characters with 1 lowercase, 1 capital letters and 1 number) and the first name of the user. Other information of the user is optional.
By setting autoLogin
to true
the user is going to be automatically logged in after a successful registration (default = false).
fun registerUser(email: String, password: String, firstName: String) {
viewModelScope.launch {
val anybillUser = AuthProvider.registerUser(email, password, firstName, autoLogin = true)
}
}
# Converting an anonymous user to an anybill user
Anonymous users have restricted access to the anybill functions (e.g. restricted amount of bills). If the app user wants to take use of all the anybill functions he has to create an anybill account. To retain the data of an anonymous user the anybill sdk provides a function to convert an anonymous user to an anybill user.
fun convertAnonymousUser(email: String, password: String, firstName: String, gender: String) {
viewModelScope.launch {
AuthProvider.convertAnonymousUserToAnybillUser(email, password, firstName, gender)
}
}
Afterwards the user can login into his anybill account using his login credentials.
# Get QR Code Data
Certain points of sale allow anybill users to receive bills by scanning a QR code on the user's phone. To get the jsonObject which should be included in the QR Code use the getQRCodeData
method.
viewModelScope.launch {
when(val qrCodeCall = AuthProvider.getQRCodeData()){
is Success -> //Success
is GenericError -> //Error Handling
is NetworkError -> //Error Handling
}
}
The function 'getQRCodeData()' returns a 'QRCodeData' object which can be converted to a JSON String using its 'toJson()' function. Most QRCode libraries using 'TEXT' as ContentType can be initialized with the produced JSON String.
More information about QR Code data can be found on the anybill developer page (opens new window).
# Logout
Logging out an user deletes all of user's app data including cached bills, authentication information and app settings (of the anybill sdk).
AuthProvider.logoutUser()
# Delete Account
Deleting an anybill account is irreversibly. The account can not be retrieved afterwards.
fun deleteUser() {
viewModelScope.launch {
anybillAuth.deleteCurrentUser()
}
}
# TokenProvider
The TokenProvider is used for internal authentication operations. The public methods should not be accessed from the app itself. It might cause problem during the anybill authentication process.
# AnybillBill
Singleton BillProvider
grants access to the anybill bill functions. Most functions of the BillProvider are suspend functions and have to be called in a coroutine scope.
# Retrieving bills
With its caching technique the anybill sdk stores the user's bills in a local database and updates them using the anybill backend when needed.
The anybill sdk provides a LiveData object bills
representing a list of bills which can be observed in your app. By calling BillProvider's updateBills()
method the LiveData object gets filled with the locally cached bills first and updated with the newly modified/added bills as soon as the API operation completed.
Sample-Code for your ViewModel:
//This LiveData can be observed in your View
val billList : LiveData<List<Bill>> = BillProvider.bills
//Call this to initialize/fill/update the LiveData object
fun updateBills(){
viewModelScope.launch{
when(val updateCall = BillProvider.updateBills()){
is Success -> // Success
is EmptySuccess -> // User has no Bill
is GenericError -> // Error Handling
is NetworkError -> // Error Handling
}
}
}
To receive all of the user's bills without the caching process the anybill sdk provides the getBills()
(includes parameters for pagination) method.
val billList: MutableLiveData<Bill> = MutableLiveData<Bill>()
fun getAllBills(){
viewModelScope.launch {
when (val billCall = BillProvider.getBills()) {
is NetworkError -> //Error Handling
is GenericError -> //Error Handling
is Success -> {
billList.postValue(billCall.value)
}
}
}
}
# Filtering by Catgeory
To filter the user's bill list by Category, call getBillsByCategory()
with the id of the Category.
fun getBillsByCategory(categoryId: String){
viewModelScope.launch {
when (val filterCall = BillProvider.getBillsFromCategory(categoryId)){
is Success -> //Success
is GenericError -> // Error Handling
is NetworkError -> // Error Handling
}
}
}
# Adding bills
The anybill SDK provides several ways to add a Bill to the users account.
QR-Code on POS-Terminal
To add a bill from the QR-Code on the POS-Terminal use a common QR-Code Reader and extract the information of the shown QR Code. The data includes an URL with the ID of the Bill which should be added to the users account. Add the bill by calling one of the listed Methods with your preferred parameter type.
//Depending on the QR Code Scanner you are going to receive a String or URL object.
//
//Format:
// https://getmy.anybill.de/#/bill/b45f4b77-9c3c-454a-8b87-08d8bbd02f7e
//
//You can choose to parse and validate the URL before using the sdk or call one of the following methods:
BillProvider.addBillByID(billID: String)
BillProvider.addBillByURL(url: URL)
BillProvider.addBillByString(url: String)
QR-Code on Mobile-Screen
To add a bill with a QR-Code on the user's phone screen use AuthProviders QR-Code Data method. (See auth module documentation)
# Exporting bills
To share or save a bill as PDF exportBillAsPDF(bill: Bill)
returns an URI of a PDF file in the cached directory of the app. The Uri can then be shared using an intent or saved on the user's device.
(Generation of the PDF file takes up to 30 sec)
Share PDF in other apps:
fun exportBillToPDF(bill: Bill) {
viewModelScope.launch {
when (val exportCall = billProvider.exportBillAsPDF(bill)) {
is ApiResult.Success -> startIntent(exportCall.value)
is ApiResult.GenericError -> //Error Handling
is ApiResult.NetworkError -> //Error Handling
}
}
}
private fun startIntent(uri: Uri) {
val intent = Intent()
intent.action = Intent.ACTION_SEND
intent.type = "application/pdf"
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(share)
}
ZIP
If the user wants to get hold of all his bills as PDF files, the anybill SDK provides an endpoint to generate a .zip file containing the user's bills and send it to his email address.
fun exportAllBills() {
viewModelScope.launch {
when(val exportCall = billProvider.exportBills()){
is ApiResult.EmptySuccess -> //Success
is ApiResult.GenericError -> //Error Handling
is ApiResult.NetworkError -> //Error Handling
}
}
}
# Deleting bills
Deleting a bill is irreversible. After deleting a bill with BillProvider.deleteBill(bill: Bill)
the LiveData object should be updated using updateBills()
.
fun deleteBill(bill: Bill) {
viewModelScope.launch {
when(val deleteBill = billProvider.deleteBill(bill)){
is ApiResult.EmptySuccess -> //Success
is ApiResult.GenericError -> //Error Handling
is ApiResult.NetworkError -> //Error Handling
}
}
}
# AnybillCategory
Singleton CategoryProvider
grants access to the anybill category functions. Most functions of the CategoryProvider are suspend functions and have to be called in a coroutine scope.
# Retrieving categories
With its caching technique the anybill sdk stores the categories in a local database and updates them using the anybill backend when needed.
The anybill sdk provides a LiveData object categories
representing a list of bills which can be observed in your app. By calling CategoryProvider's updateCategories()
method the LiveData object gets filled with the locally cached category first and updated with the newly modified/added categories as soon as the API operation completed.
Sample-Code for your ViewModel:
//This LiveData can be observed in your View
val categoryList : LiveData<List<Category>> = CategoryProvider.categories
//Call this to initialize/fill/update the LiveData object
fun updateCategories(){
viewModelScope.launch{
CategoryProvider.updateCategories()
}
}
If you don't want to use Live Data objects, you can use updateAndGetAllCategories()
which will return all Anybill Categories from the API.
# Getting a category by id
Retrieving a single Category object by its ID can be done with the getCategoryById()
functions.
fun getCategoryById(id: String){
when(val categoryCall = CategoryProvider.getCategoryById(id)){
is ApiResult.Success -> //Continue with categoryCall.value
is ApiResult.GenericError -> //Error Message
is ApiResult.NetworkError -> //Error Message
}
}
# Matching a store name with a category
When creating a ScanBill the name of the vendor can be set manually. Using getCategoriesByName() the vendor name can be matched with a BillCategory/List of BillCategory which then can be displayed to the user as a suggestion.
fun getCategoryByName(name: String){
when(val categoryCall = CategoryProvider.getCategoryByName(name){
is ApiResult.Success -> //Continue with categoryCall.value
is ApiResult.GenericError -> //Error Message
is ApiResult.NetworkError -> //Error Message
}
}
# AnybillStore
The store module provides information about stores that support the anybill system, or will support it in the near future. The stores and details are available via the StoreProvider
.
# Retrieving stores
To retrieve stores from the StoreProvider
the method
getStores(skip: Int?, take: Int?, creationTypeFilter: Set<BillCreationType>): LiveData<List<AnybillStore>>
is used.
The SDK uses caching and stores all stores, once they have been fetched from the backend.
It returns a LiveData that publishes the locally cached stores and automatically updates, if the cached stores change.
The parameters skip and take can be optionally used for pagination. If they are null the whole list of all supported stores is being returned. If at least one of them is not null, pagination is applied and the first skip
stores on the list of stores ordered by vendor name are being skipped and take
stores are being fetched from the database. So the overall result is a list of length take
with an offset in the stores list of skip
. Whenever take
is null it is treated as infinite.
The parameter creationTypeFilter
is used to optionally retrieve only stores that support a specific method of bill creation (e.g. a store might not support displaying QR codes and therefore only gives out receipts via scanning the code off the smartphone display).
If the parameter is null, no filtering is applied and all stores can be retrieved. If the parameter is not null only stores which match at least one of the specified BillCreationTypes are being returned.
To fetch the latest list of stores from the backend the method updateStores(billCreationTypes: Set<BillCreationType>): ApiResult<Unit>
is used.
It returns an ApiResult that indicates if the network request succeeded or not. The optional parameter billCreationTypes can be used to filter the stores as previously mentioned. The difference is that this parameter causes filtering at request level, only returning stores that match the filter and therefore reducing bandwidth and local cache size. However if this parameter is not empty and therefore filtering is applied, there is no possibility to retrieve stores with other BillCreationTypes anymore until you call this method again with a different or no filter at all. This is intended for SDK integrations where the app only needs to support stores that all have the same receipt creation capabilities.
Sample-code for your viewmodel:
fun updateStores() {
viewModelScope.launch {
storeProvider.updateStores()
}
}
fun getStores(billCreationTypes: Set<BillCreationType> = setOf()) {
_currentLifeData?.let {
_storesToDisplay.removeSource(it)
}
viewModelScope.launch {
val result = storeProvider.getStores(creationTypeFilter = billCreationTypes)
_currentLifeData = result
_storesToDisplay.addSource(result) {
_storesToDisplay.postValue(it)
}
}
}
Where _storesToDisplay is a MediatorLiveData of Type List<AnybillStore>
# Retrieving store details
The AnybillStore
data model does not return all available information about a store. Some properties of a store have to be queried separately via the
getStoreDetails(storeId: String): LiveData<AnybillStoreDetails?>
method.
This method takes the ID of an AnybillStore
and returns an observable LiveData which also automatically updates, if the cached value of this AnybillStoreDetails
changes.
To update the local value of the store details use the method updateStoreDetails(storeId: String): ApiResult<AnybillStoreDetails?>
.
It returns an ApiResult that indicates if the network request succeeded or not. In the case of a successfully executed network request contains the store details in the ApiResult
value property, if you prefer to not use the LiveData returned by getStoreDetails
.
Sample-code for your viewmodel:
fun getStoreDetail(storeId: String): LiveData<AnybillStoreDetails?> {
return storeProvider.getStoreDetails(storeId)
}
fun updateStoreDetails(storeId: String) {
viewModelScope.launch {
storeProvider.updateStoreDetails(storeId)
}
}
# AnybillLog
The Log Module is used by other modules to log internal errors of the anybill SDK to Datadog. No information of your users or your app are being logged. Accessing the Log module's functions in your app code might affect support and analytics processes.
# AnybillOCR
Singleton OCRProvider
grants access to the anybill ocr functionalities. Most functions of the OCRProvider are suspend functions and have to be called in a coroutine scope.
# Adding a bill using the OCR feature
The OCR feature allows users to scan a paper receipt using their smartphone camera. The provided image is processed and converted into a ScanBill model. To add a bill using the OCR feature follow these steps:
- Uploading image of paper receipt
To upload an image of the paper receipt retrieve an image using the camera or media option of the device and use the addBillWithOCRScan()
function with an InputStream
of the image which should be scanned.
fun getOCRScanForImage(inputStream: InputStream) {
viewModelScope.launch {
when (val ocrScanCall = OCRProvider.addBillWithOCRScan(inputStream)) {
is ApiResult.Success -> // Continue editing ScanBillDTO from ocrScanCall.value
is ApiResult.GenericError -> //Error Handling
is ApiResult.NetworkError -> //Error Handling
}
}
}
OCR Scan can take up to 20 seconds.
- Editing and finalizing the new bill
The returned ScanBillDTO
contains all values which were recognized by the OCR Scan. These values can now be edited and/or extended.
To finalize the bill, call updateScannedBill()
with the received/extended DTO object.
//Several ways to add a LineItem using the extension functions ScanBillDTO.addLineItem()
scanBillDTO.sellerName = "YourVendor"
scanBillDTO.addLineItem(10.0, 2.0, QuantityMeasure.Count, "Item")
val lineItem = LineItemDTO(null, 1.0, QuantitityMeasure.Kilogram, 3.45)
scanBillDTO.addLineitem("Item" ,lineItem)
viewModelScope.launch {
when (val ocrScanCall = ocrProvider.updateScannedBill(scanBillDTO)) {
is ApiResult.Success -> // Success
is ApiResult.GenericError -> //Error Handling
is ApiResult.NetworkError -> //Error Handling
}
}
# Manually creating a Scanbill without image
ScanBills can also be created manually without using the OCR feature by creating an instance of ScanBillDTO
with the required values and using addBillWithoutScan()
.
fun createNewBill(){
val newBill = ScanBillDTO(
sellerName: "Seller",
date: "2021-07-19T13:10:06.8684491+00:00",
fullAmountInclVat: 10.0,
foreignCurrency: "EUR",
categoryId: "fa93e0bb-49b0-4077-1681-08d8be38cdb0",
paymentTypeInformation: PaymentTypeInformationDTO(10.0, PaymentType.Cash)
)
viewModelScope.launch {
when (val addCall = ocrProvider.addBillWithoutScan(newBill)) {
is ApiResult.Success -> // Success
is ApiResult.GenericError -> //Error Handling
is ApiResult.NetworkError -> //Error Handling
}
}
}
# Retrieving an image of a ScanBill
To display the image of a previously added ScanBill use getImageOfScannedBill()
. On success this method returns a ByteArray containing the file information of the image. This can then be converted to a Bitmap and displayed to the user.
val bitMap : MutableLiveData<Bitmap> = MutableLiveData()
fun getImageOfBill(billId: String){
viewModelScope.launch {
when (val getCall = ocrProvider.getImageOfScannedBill(billId)) {
is ApiResult.Success -> createBitMapFromBytes(getCall.value)
is ApiResult.GenericError -> //Error Handling
is ApiResult.NetworkError -> //Error Handling
}
}
}
private fun createBitMapFromBytes(bytes: ByteArray){
val bitmap: Bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
bitMap.postValue(bitmap)
}
# AnybillWarranty
The warranty module provides methods to manage user's warranties. Warranties can be set for a bill or individual line items and can be connected to a push notitication reminder.
# Retrieving and updating warranties
With its caching technique the anybill sdk stores the warranties in a local database and updates them using the anybill backend when needed.
The anybill sdk provides a Live Data object representing a list of warranties which can be observed in your app. By calling WarrantyProvider's updateWarranties()
method the observable object gets filled with the locally cached warranties first and updated with the newly modified/added warranties as soon as the API operation completed.
Sample-Code for your ViewModel :
//This LiveData can be observed in your View
val categoryList : LiveData<List<Category>> = WarrantyProvider.warranties
//Call this to initialize/fill/update the LiveData object
fun updateWarranties(){
viewModelScope.launch{
WarrantyProvider.updateWarranties()
}
}
If you don't want to use Live Data object, you can use getNetworkWarranties()
which will return all Warranties of the user.
# Adding a new warranty
Using the WarrantyDTO
struct, you can create a new warranty for a user's bill:
* `articleName`: Specifies the article's name (Optional)
* `billId`: The bill's id (Required)
* `lineItemId`: The lineitem's id (Optional)
* `warrantyDate`: Warranty's due date in ISO 8601 (Required)
* `reminders`: Reminder object (Optional)
Each LineItem and articleName can only have one Warranty
.
The warranty's due date can not be in the past.
val warrantyDTO = WarrantyDTO(
articleName = "Apple",
billid = "ce763c6a-4ae8-4l7c-df7f-08d9dfc2g23a",
lineItemId = null,
warrantyDate = "2021-12-31T14:00:00Z",
reminders = emptyList())
when(val warrantyCall = WarrantyProvider.addWarranty(warrantyDTO)){
is NetworkError -> //Error Handling
is GenericError -> //Error Handling
is Success -> {
warrantyCall.value //Further implementation
}
}
# Activating / deactivating notifications for a specific Warranty
Notifications for each Warranty can be activated or deactivated with updateWarrantyNotificationIsActivated()
.
This method takes two parameters:
* `warrantyId`: The warranty's id (Required)
* `activated`: Boolean indicating the desired activation status (Required)
when(val notificationUpdateCall = WarrantyProvider.updateWarrantyNotificationIsActivated(warrantyId, activated)){
is NetworkError -> //Error Handling
is GenericError -> //Error Handling
is Success -> {
notificationUpdateCall.value //Further implementation using the updated Warranty object
}
}
# AnybillTree
Offers a „tree level system“ to enhance the motivation of the user by planting trees for scanned receipts.
# Retrieving all Tree Levels
To retrieve a list of all TreeLevels use the getAllTreeLevels()
functions
viewModelScope.launch {
when (val treeCall = TreeProvider.getAllTreeLevels()) {
is ApiResult.Success -> treeCall.value
is ApiResult.GenericError -> //Error Handling
is ApiResult.NetworkError -> //Error Handling
}
}
# Get Trees planted by user
Returns the amount of trees which were planted by the user's bills
viewModelScope.launch {
when (val treeCall = TreeProvider.getTreesPlantedByUser()) {
is ApiResult.Success -> treeCall.value
is ApiResult.GenericError -> //Error Handling
is ApiResult.NetworkError -> //Error Handling
}
}
# Retrieving complete Tree Info
Information like the amount of trees planted by the user, the total amount of trees planted and the tree levels can all be accessed separately.
By calling getTreeInfo()
all this information can be retrived within one call.
viewModelScope.launch {
when (val treeCall = TreeProvider.getTreeInfo()) {
is ApiResult.Success -> treeCall.value
is ApiResult.GenericError -> //Error Handling
is ApiResult.NetworkError -> //Error Handling
}
}
# AnybillDocsManager
Allows the export of receipts directly to a document manager like GetMyInvoices (opens new window) or Fileee (opens new window).
# Fileee
Fileee enables mutliple document related features:
- Export receipts to fileee directly and automatically with one click
- Link email accounts and services such as Dropbox or GoogleDrive
- All private documents in a single overview
- Share receipts and documents with your family and friends
The anybill sdk provides the possibility to link an anybill account to an existing Fileee account and export anybill bills to the fileee storage. By starting an external authentication process in a web view the user can login into his Fileee account and grant the permission to bind both accounts.
To start the authentication process call startAuthorizationRequest(context: Context, responseActivity: Class<*>)
of the FileeeProvider
.
responseActivity
being the Activity which should be called when the authentication process finished or the user exists the web view.
viewModelScope.launch {
when (val fileeeRequest = FileeeProvider.startAuthorizationRequest(context, MainActivity::class.java)) {
is EmptySuccess -> // Success (Auth Process started)
is GenericError -> // Error Handling
is NetworkError -> // Error Handling
}
}
To set your application as a receiver of the redirect set the redirect scheme in your app's build gradle:
android {
...
manifestPlaceholders = [
'appAuthRedirectScheme': 'de.fileee'
]
...
}
In the onResume()
of your responding Activity catch the Intent sent by the redirecting web view:
//In your Activity
override fun onResume() {
super.onResume()
if (intent != null){
viewModel.checkForFileeeAuthResponse(intent)
}
}
//In your ViewModel:
fun checkForFileeeAuthResponse(intent: Intent) {
viewModelScope.launch {
when (val fileeeRequest = FileeeProvider.checkForAndHandleFileeeAuthResponse(intent)) {
is ApiResult.Success -> // Successfully connected accounts
is ApiResult.EmptySuccess -> // No Auth Response found
is ApiResult.GenericError -> // Error Handling
is ApiResult.NetworkError -> // Error Handling
}
}
}
# Export bills to Fileee
To export an anybill bill to the fileee documents use exportBillsToFileee(billIds: ArrayList<String>)
.
val billIds = ["c2c613b7-7ba2-47e7-8b94-08d8bbd02f7e", "2a0af38a-6d45-43cc-8b95-08d8bbd02f7e"]
viewModelScope.launch {
when (val fileeeRequest = FileeeProvider.exportBillsToFileee(billIds) {
is ApiResult.Success -> // Successfully exported bills
is ApiResult.GenericError -> // Error Handling
is ApiResult.NetworkError -> // Error Handling
}
}
# GMI
GetMyInvoice is a central invoice management software for your business with several features:
- Invoice management with automatic download
- Export receipts to GetMyInvoices directly and automatically with one click
- Overview of all exported receipts
- Export to many accounting tools
With the anybill sdk anybill accounts can either be binded with an existing GMI Account or connected by creating a new GMI Account.
# Binding with an existing GMI Account
To bind an anybill account with an existing GMI Account, the user has to manually copy an API Key from its GMI Account:
To access the API Key the user has to follow these steps:
- Login on the GMI page: https://de4-login.getmyinvoices.com/api_access.php
- Click on the plus symbol in the top left corner
- Select your name
- Click Save
- Copy the API Key shown next to your name
Link both accounts by using updateGMIAccountApiKey(apiKey: String)
with the received API Key as parameter.
viewModelScope.launch {
when (GMIProvider.updateGMIAccountApiKey(apiKey)) {
is EmptySuccess -> // Successfully updated GMI api key
is GenericError -> // Error Handling
is NetworkError -> // Error Handling
}
}
# Create a new GMI Account
The anybill sdk also enables to create a new GMI Account using the anybill app API. To create a GMI Account use the GMIAccountDTO
model.
val gmiAccount = GMIAccountDTO(
companyName = "exampleName",
country = 76,
email = "example@anybill.de,
firstName = "example",
language = GMILanguageDTO.DE,
password = "example"
)
viewModelScope.launch {
when (GMIProvider.createGMIAccount(gmiAccount)) {
is EmptySuccess -> // GMI Account was successfully created
is GenericError -> // Error Handling
is NetworkError -> // Error Handling
}
}
# Exporting bills to GMI
To export an anybill bill to GMI use exportBillsToGMI(billIds: ArrayList<String>)
:
val billIds = ["c2c613b7-7ba2-47e7-8b94-08d8bbd02f7e", "2a0af38a-6d45-43cc-8b95-08d8bbd02f7e"]
viewModelScope.launch {
when (val fileeeRequest = FileeeProvider.exportBillsToGMI(billIds) {
is ApiResult.Success -> // Successfully exported bills
is ApiResult.GenericError -> // Error Handling
is ApiResult.NetworkError -> // Error Handling
}
}
# AnybillUBA
UBA (Universal Banking Access) Module allows users to create a connection to multiple bank accounts via PSD2 interface for a full financial overview.
# Connecting and managing bank accounts
To connect a new bank account and/or manage already connected bank accounts anybill provides a webview interface. To open the webview interface you have to register an ActivityResultLauncher in your Fragment/Activity. You can handle the result of the UBA Webview in this ResultLauncher
// In your Activity/Fragment
private val resultLauncher = this.registerUBAResultLauncher { ubaState ->
when (ubaState) {
UBAState.SUCCESSFULLY_LINKED -> //Success
UBAState.FAILED_LINKING -> //Eror Handling
}
}
To start the UBA Webview use manageBankAccounts
of the UBAProvider
with the registered ResultLauncher and the Context
of the View you are calling from. You can determine wether the Webview should directly open a bank login by setting the optional Boolean addBankLogin
to true (e.g. if the user hasn't connected a bank account yet). Leaving the Boolean on the default value will open a bank account overview where the user can manage his current bank accounts.
//In your ViewModel
fun manageBankAccounts(launcher: ActivityResultLauncher<Intent>, context: Context) {
viewModelScope.launch {
when(val ubaManageCall = UBAProvider.manageBankAccounts(launcher, context, true)){
is ApiResult.EmptySuccess -> //Webview was started
is ApiResult.GenericError -> //Error Handling
is ApiResult.NetworkError -> //Error Handling
}
}
}
# Retrieving transactions
To retrieve all connected bank accounts including their psd2 transactions use getBankAccountsWithTransaction()
.
viewModelScope.launch {
when (val bankAccounts = UBAProvider.getBankAccountsWithTransaction()) {
is ApiResult.Success -> //Success
is ApiResult.GenericError -> //Error Handling
is ApiResult.NetworkError -> //Error Handling
}
}
# Synchronize bank accounts
Due to legal reasons every PSD2 connection has to be reconnected manually every 90 days. The returned bank accounts from getBankAccountsWithTransaction()
contain a Boolean flag isSynced
. If the Boolean value is false
the bank account has be reconnected by the app user. To open a Webview where the user can synchronize his bank accounts use syncBankAccount()
.
Register an ActivityResultLauncher in your Fragment/Acitivity
// In your Activity/Fragment
private val resultLauncher = this.registerUBAResultLauncher { ubaState ->
when (ubaState) {
UBAState.SUCCESSFULLY_LINKED -> //Success
UBAState.FAILED_LINKING -> //Eror Handling
}
}
Start the Webview using syncBankAccount()
with the bankId
of the bank which should be synchronized, the registered ResultLauncher and the Context
of the View you are calling from.
//In your ViewModel
fun syncBankAccounts(bankId: String, launcher: ActivityResultLauncher<Intent>, context: Context) {
viewModelScope.launch {
when(val syncCall = UBAProvider.syncBankAccount(bankId, launcher, context)){
is ApiResult.EmptySuccess -> //Webview was started
is ApiResult.GenericError -> //Error Handling
is ApiResult.NetworkError -> //Error Handling
}
}
}
# Linking bills and transactions
Using the anybill's UBA Module an anybill user can link a bill with a transaction. The linked IDs are then included in the Bill/Transaction Model.
viewModelScope.launch {
when (val linkCall = UBAProvider.linkTransactionWithBill(transactionId, billId)) {
is ApiResult.EmptySuccess -> //Success
is ApiResult.GenericError -> //Error Handling
is ApiResult.NetworkError -> //Error Handling
}
}
# Unlink bills and transactions
Linked bills/transactions can be unlinked using the provided methods of the UBAProvider
.
UBAProvider.unlinkBillByTransactionId(transactionId)
//or
UBAProvider.unlinkBillByBillId(billId)
# AnybillExpenses
Coming Soon
# AnybillContentArea
The Content Area Module enables the Content Area feature, which enables the displaying of additional information (e.g. advertisement or news) on receipts. With this module the data of these content areas can be queried to display them in the SDK implementing app. Another possibility is to use this in conjunction with the Content Area UI Module for a working implementation out of the box.
# Retrieving a content area
Instances of ContentArea can be retrieved through the ContentAreaProvider. This Provider exposes the method getContentArea(contentAreaId: String), returning a LiveData of type content area which updates automatically, if an update is requested. The methods only parameter is the ID of the content area to fetch, which can be found in the bill data model. The SDK utilizes caching and returns a local copy of the latest cached value.
Sample code for your viewmodel:
private val _contentArea: MediatorLiveData<ContentArea?> =
MediatorLiveData<ContentArea?>()
private var currentContentAreaLiveData: LiveData<ContentArea?>? = null
private val anybillContentAreaProvider = ContentAreaProvider
fun loadContentAreaById(contentAreaId: String) {
if (_contentArea.hasObservers()) {
currentContentAreaLiveData?.let {
_contentArea.removeSource(it)
}
}
_contentArea.addSource(anybillContentAreaProvider.getContentArea(contentAreaId)) {
_contentArea.postValue(it)
}
}
# Updating a content area
In order to fetch the latest content area data from the backend, the ContentAreaProvider exposes the method updateContentArea(contentAreaId: String), returning an ApiResult of Type ContentArea. The resulting ApiResult indicates, whether or not the backend request succeeded or failed. In case of a successful request the local cache is automatically updated and the new data automatically published via the aforementioned LiveData.
viewModelScope.launch {
handleApiResult(anybillContentAreaProvider.updateContentArea(contentAreaId))
}
# AnybillContentAreaUI
The Content Area UI module provides a UI implementation of the content area functionality. This module requires the UI module to work.
# Adding the content area UI Module
To add the functionalities of this module, no further actions are required besides declaring the dependency in your gradle files. Due to technical limitations, the dependency of this module has to appear before the UI Module dependency in your build.gradle.
Possible entries in build.gradle:
implementation 'de.anybill.anybill_android_sdk:contentareaui:<VERSION>'
implementation 'de.anybill.anybill_android_sdk:ui:<VERSION>'
# AnybillUI
The UI Module provides a full navigation graph including anybill registration/login, receipts, shops and user settings. It is using the latest Kotlin Android development tools like the navigation component, Koin and ViewBinding. We recommend updating your app to use ViewBinding and Navigation Component before integrating this module.
# Initialize the UI Module
In the onCreate()
of your Application
class insert the Koin module initialisization and the AnybillUI Builder.
class YourApp : Application() {
override fun onCreate() {
super.onCreate()
///Koin is a transitive dependency of the ui module so you can use the `startKoin` method without adding
///a depency to your build.gradle
///If you are already using Koin in your App add the `anybillUIModule` together with your existing Koin modules
///e.g. modules(listOf(yourModule, anybillUIModule))
startKoin {
androidContext(this@YourApp)
modules(anybillUIModule)
}
///Initialize the AnybillUI with your application id (needed for file operations) and optional customization methods.
AnybillUI.Builder(BuildConfig.APPLICATION_ID).build()
}
}
# Setup the View for the UI Modul
The UI Module is using the navigation controller and therefore has to be located in a NavHostFragment.
# Implement without any menu
If you don't use the navigation component yourself or want to start the anybill UI Module in an own View without any menu, setup the fragment view as shown below:
anybill_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<fragment
android:id="@+id/anybill_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"
app:navGraph="@navigation/anybill_nav_graph"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
AnybillFragment.kt
class AnybillFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.anybill_fragment, container, false)
}
}
YourCallingFragment.kt
yourCallingButton.setOnClickListener {
requireActivity().supportFragmentManager.commit {
setReorderingAllowed(true)
replace<AnybillFragment>(R.id.container)
}
}
# Implement the UI Module in a Bottom Navigation View
Setup the view for your bottom navigation. The FragmentView used to display your bottom nav destination has to be a NavHostFragment
.
Your activity_main.xml
could look like this:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbarlayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/toolbar"
android:minHeight="?attr/actionBarSize"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/nav_graph_main"
app:layout_constraintBottom_toTopOf="@+id/bottom_nav"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbarlayout"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/your_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>
Implement your BottomNavigation menu with the exact name of the anybill navigation graph anybill_nav_graph
.
your_menu.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/nav_graph_home"
android:icon="@drawable/ic_home"
android:title="@string/home_title" />
<item
android:id="@+id/anybill_nav_graph"
android:icon="@drawable/ic_anybill"
android:title="@string/anybill_title"
/>
<item
android:id="@+id/nav_graph_user"
android:icon="@drawable/ic_user"
android:title="@string/user_title" />
</menu>
Initialize the bottom navigation in your MainActivity.kt:
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
initBottomNav()
}
private fun initBottomNav() {
// Get NavHostFragment and setup the VavController
val navHostFragment = supportFragmentManager.findFragmentById(
R.id.nav_host_fragment
) as NavHostFragment
navController = navHostFragment.navController
//Initialize your bottom navigation
val bottomNavigationView = binding.bottomNav
bottomNavigationView.setupWithNavController(navController)
// Setup the top destinations of your bottom navigation. In this example every navigation graph represents one top destination
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.nav_graph_home,
R.id.anybill_nav_graph,
R.id.nav_graph_user
)
)
// Add a destination listener to your navController to display/hide bottom navigation on specific fragments.
// The anybill fragments in this example include the authorization process and the main home fragment
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.homeFragment -> showBottomNav()
R.id.userFragment -> showBottomNav()
de.anybill.anybill_android_sdk.ui.R.id.anybillRegisterFragment -> showBottomNav()
de.anybill.anybill_android_sdk.ui.R.id.anybillLoginFragment -> showBottomNav()
de.anybill.anybill_android_sdk.ui.R.id.anybillCodeVerificationFragment -> showBottomNav()
de.anybill.anybill_android_sdk.ui.R.id.anybillHomeFragment -> showBottomNav()
de.anybill.anybill_android_sdk.ui.R.id.anybillHostFragment -> showBottomNav()
else -> hideBottomNav()
}
}
//Initialize your action bar
setupActionBarWithNavController(navController, appBarConfiguration)
}
private fun hideBottomNav() {
binding.bottomNav.visibility = View.GONE
}
private fun showBottomNav() {
binding.bottomNav.visibility = View.VISIBLE
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration)
}
}
# Implement the UI Module in a DrawerLayout
Similiar to the BottomNavigation you have to set the anybill_nav_graph
as an item in your menu.
Your activity_main.xml
could look like this:
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!-- Layout to contain contents of main body of screen (drawer will slide over this) -->
<androidx.fragment.app.FragmentContainerView
android:name="androidx.navigation.fragment.NavHostFragment"
android:id="@+id/nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:navGraph="@navigation/nav_graph" />
<!-- Container for contents of drawer - use NavigationView to make configuration easier -->
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:menu="@menu/menu"
android:fitsSystemWindows="true" />
</androidx.drawerlayout.widget.DrawerLayout>
Implement your Drawer menu with the exact name of the anybill navigation graph anybill_nav_graph
.
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/mainFragment"
android:title="Home" />
<item
android:id="@+id/anybill_nav_graph"
android:title="Digital Receipts" />
</menu>
Initialize the drawer layout in your MainActivity.kt using the navigation component methods:
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var navController: NavController
private lateinit var binding: MainActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = MainActivityBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
initDrawerLayout()
}
private fun initDrawerLayout() {
// Get NavHostFragment and setup the VavController
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
// Create appBarConfiguartion including your top level destinations
// The anybill fragments in this example include the authorization process and the main home fragment
appBarConfiguration = AppBarConfiguration(
topLevelDestinationIds =
setOf(
R.id.mainFragment,
de.anybill.anybill_android_sdk.ui.R.id.anybillRegisterFragment,
de.anybill.anybill_android_sdk.ui.R.id.anybillLoginFragment,
de.anybill.anybill_android_sdk.ui.R.id.anybillCodeVerificationFragment,
de.anybill.anybill_android_sdk.ui.R.id.anybillHomeFragment,
de.anybill.anybill_android_sdk.ui.R.id.anybillHostFragment,
), binding.drawerLayout
)
//Initialize the action ba
setupActionBarWithNavController(navController, appBarConfiguration)
//Initialize your navigation view
binding.navView.setupWithNavController(navController)
}
//Handle drawer navigation
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(navController, binding.drawerLayout)
}
}
More information can be found in the [android documentation].(https://developer.android.com/guide/navigation/navigation-ui#Tie-navdrawer)
# Customize the UI Module behaviour
The AnybillUI
Builder provides methods to customize the anybill UI module and its behaviour. Use the following methods in your Application
class when initializing the anybill SDK.
# Custom Theme (Color, Font)
To customize the look of the UI Module views you can override specific values in a own custom theme.
.setCustomTheme(R.style.YourCustomTheme)
# Disable default functions
By default the anybill UI Module provides the functions to view the user's QR Code, receive receipts by scanning other QR Codes, a list of available anybill stores and user settings. If for some reason you don't want to include these functions in your custom UI Module you can disable them using the following functions.
Use this to disable Profile function in the UI Module if for example you use your own app user to authenticate in the anybill sdk and are taking use of the TokenUser
in the auth module.
.disableProfile()
Use this to disable the function to receive receipts by scanning the user's QR Code.
.disableQRCode()
Use this to disable the function to scan QR Codes on the POS-Screen. This could be disabled if you for example only take use of receiving receipts by Customer/Creditcard.
.disableScan()
Use this to disable the shop list function if for example you already have all your shops displayed in your app and/or don't want to display other anybill stores
.disableShops()
# Customize Action Bar
The UI module edits your action bar on specific screens. You can customize this behaviour of the anybill UI module by following methods:
By default the anybill SDK sets custom action bar titles on specific pages. If you don't want to use custom titles disable this function:
.disableActionBarTitles()
For some functions the anybill SDK adds Buttons to the ActionBar. You can customize the color of the actionbar icons by using following function:
.setActionBarButtonColor(R.color.yourColor)
# Custom anybill Theme
As mentioned before the anybill UI Module can be customized using your own colors and fonts. Create a custom Theme in themes.xml
extending the AnybillAppTheme
and use it in the initialization function of the UI Module.
<style name="CustomAnybillTheme" parent="AnybillAppTheme">
// Override the primary/accent color of the UI Modul
<item name="colorPrimary">@color/yourCustomColor</item>
<item name="colorPrimaryDark">@color/yourCustomColor</item>
<item name="colorAccent">@color/yourCustomColor</item>
/// Set the background color
<item name="android:windowBackground">@color/yourCustomColor</item>
// Set a custom font
<item name="android:fontFamily">@font/yourFont</item>
<item name="fontFamily">@font/yourFont</item>
</style>
Set the custom Theme in your Application
class.
.setCustomTheme(R.style.YourCustomTheme)