# Android Integration

Getting Started
Push Notification
AnybillCore
AnybillAuth
AnybillBill
AnybillCategory
AnybillStore
AnybillLog
AnybillOCR
AnybillWarranty
AnybillTree
AnybillDocsManager
AnybillUBA
AnybillExpenses
AnybillContentArea
AnybillContentAreaUI
AnybillUI

# 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:

  1. 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}
            }
        }
    }
}
  1. 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
                }
            }
        }
    }

Back to top

# 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:

  1. Create a notification handler:
class YourNotificationHandler(private val context: Context) : AnybillMessagingHandler {

    override fun onAnybillMessageReceived(remoteMessage: RemoteMessage) {
        // Display Notification
    }
    
}
  1. 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:

  1. Create an notification handler:
class YourNotificationHandler(private val context: Context) : AnybillMessagingHandler {

    override fun onAnybillMessageReceived(message: RemoteMessage) {
        // Display Notification
    }
    
}
  1. 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.

  1. 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.

Back to top

# 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 getQRCodeDatamethod.

    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.

Back to top

# 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

PDF

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
            }
        }
    }

Back to top

# AnybillCategory

Singleton CategoryProvidergrants 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
      }
    }

Back to top

# 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)
    }
}

Back to top

# 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.

Back to top

# AnybillOCR

Singleton OCRProvidergrants 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:

  1. 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.

  1. 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)
}

Back to top

# 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
            }
        }

Back to top

# 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
        }
    }

Back to top

# 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:

  1. Login on the GMI page: https://de4-login.getmyinvoices.com/api_access.php
  2. Click on the plus symbol in the top left corner
  3. Select your name
  4. Click Save
  5. 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
    }
}

Back to top

# 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
    }
}

Linked bills/transactions can be unlinked using the provided methods of the UBAProvider.


UBAProvider.unlinkBillByTransactionId(transactionId)

//or 

UBAProvider.unlinkBillByBillId(billId)

Back to top

# 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))
        }

Back to top

# 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>'

Back to top

# 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)

Back to top