# Android Integration

# General Information

Getting Started
Push Notification
Anybill App Link

# Base Modules

AnybillBase
AnybillCore (deprecated)
AnybillAuth (deprecated)
AnybillBill (deprecated)
AnybillCategory (deprecated)
AnybillLog (deprecated)

# Additional Modules

AnybillStore
AnybillContentArea

# UI Modules

AnybillUI
AnybillContentAreaUI
AnybillStoreUI

# Getting Started

Breaking changes when updating to AnybillBase

With the latest update log, core, auth, bill and category were merged together to the new base module.

Breaking changes:

  • Update minSdk Version to SDK 21
  • Refactored structure of SDK
  • Rename LoggerDD to AnybillLogger

To migrate to the base module, we recommend using the Auto-Import tool of your IDE.

# 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 base module required for the basic usage of the SDK
dependencies {
    implementation 'de.anybill.anybill_android_sdk:base:{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>

# Enable java.time for device under API 26

If you are targeting devices under API 26, we recommend enabling java.time usage for lower API usages. Add following line to your compile options in your app build.gradle:


    compileOptions {
        isCoreLibraryDesugaringEnabled = true
        ...
    }

Exceptions

As our SDK has a minimum API Version of 24, we still support API 24 and 25. However without enabling java.time exceptions during saving of Tokens may occur.

# 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
                        INVALID_REFRESH_TOKEN -> //Error Handling
                    }       
                }
                is Success -> {
                    userInfoTask.value //Further implementation
                }
            }
        }
    }

Back to top

The anybill SDK supports deep linking into your app from the anybill receipt website. The deep link either opens the app directly (if installed) or persists the data over an app installation. You can utilize this feature to redirect users to your app, acquire new users and add the receipt from the receipt website to an account.

To enable anybill AppLink, follow these steps:

  1. Acquire Applink URL provided by anybill with your unique path pattern by contacting us beforehand:

<intent-filter android:label="@string/app_name" android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />

    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />

    <!-- Add your unique path pattern provided by anybill -->
    <data android:scheme="https" android:host="applink.anybill.de"
        android:pathPattern="/${your_path_pattern}" />
</intent-filter>

  1. Generate Digital Asset Links file using the Android Link Assistant:

Android Link Assistant (opens new window)

  1. Provide the returned file to anybill:

[
  {
    "relation": [
      "delegate_permission/common.handle_all_urls"
    ],
    "target": {
      "namespace": "android_app",
      "package_name": "de.anybill.ui_module_integration_example",
      "sha256_cert_fingerprints": [
        "5D:78:62:8E:4B:6A:E8:33:BE:9A:94:0B:7D:24:30:4E:79:DF:D3:8B:E7:0C:42:8B:FD:72:3F:1D:36:BC:F6:C3"
      ]
    }
  }
]


⚠️ To enable anybill AppLink for your debug and release version, you'll have to generate multiple files and provide anybill both of them!

  1. Insert code to handle incoming app links:

In your MainActivity:


// Handle incoming AppLinks when app was installed and not in background
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    //...

    // Check if app was opened by a deep link (AnybillAppLink) and forward the included receipt ID
    // to the UI module
    AnybillAppLink.checkForAnybillAppLinkIntent(intent) { data ->
        if (data != null) {
            //Add Bill
        }
    }
}


// ...

// Handle incoming AppLinks when app was installed and in background
override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    intent?.run {
        // Check for app being opened by AnybillAppLink
        AnybillAppLink.checkForAnybillAppLinkIntent(intent) { data ->
            if (data != null) {
                //Add Bill
            }
        }
    }
}

// ...

// Handle incoming AppLinks when app was not installed and is opened for the first time
override fun onWindowFocusChanged(hasFocus: Boolean) {
    super.onWindowFocusChanged(hasFocus)
    AnybillAppLink.checkForAnybillAppLinkData(this) { data ->
        if (data != null) {
            //Add Bill
        }
    }
}

# 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

# AnybillBase

# Authentication

The Base 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.INVALID_REFRESH_TOKEN -> {
                // 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.

# Bill

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

# Category

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

# AnybillCore

The functionality of the base modules were merged together into the new AnybillBase module

Back to top

# AnybillAuth

The functionality of the base modules were merged together into the new AnybillBase module

Back to top

# AnybillBill

The functionality of the base modules were merged together into the new AnybillBase module

Back to top

# AnybillCategory

The functionality of the base modules were merged together into the new AnybillBase module

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 functionality of the base modules were merged together into the new AnybillBase module

Back to top

# 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

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

YourAnybillFragment.kt


class YourAnybillFragment : 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<YourAnybillFragment>(R.id.anybill_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 and design

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 the 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 base 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 make 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:

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)

To disable the editing of the ActionBar and move related actions into the UI Views, disable the ActionBar usage.

.disableActionBarUsage()

# Custom anybill Styles

The UI Module provides two different styles tagerting merchants and vendors. These will toggle the look of the bill and shop list by displaying different information.

.setUIModuleStyle(AnybillUIModuleStyle.Vendor)

module_colors module_colors

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

module_colors module_colors

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

Insert code in your MainActivity to handle incoming app links and directly open the UI Module:


// Handle incoming AppLinks when app was installed and not in background
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    //...

    // Check if app was opened by a deep link (AnybillAppLink) and forward the included receipt ID
    // to the UI module
    AnybillAppLink.checkForAnybillAppLinkIntent(intent) { data ->
        if (data != null) {
            navController.createDeepLink()
                .setGraph(R.navigation.nav_graph_main) // Your main navigation graph
                .setDestination(R.id.anybill_nav_graph) // anybill main navigation graph
                .setArguments(data.toDeepLinkBundle()).createPendingIntent()
                .send() // Map data to bundle and navigate to UI Module 
        }
    }
}


// ...

// Handle incoming AppLinks when app was installed and in background
override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    intent?.run {
        // Check for app being opened by AnybillAppLink
        AnybillAppLink.checkForAnybillAppLinkIntent(intent) { data ->
            if (data != null) {
                navController.createDeepLink()
                    .setGraph(R.navigation.nav_graph_main) // Your main navigation graph
                    .setDestination(R.id.anybill_nav_graph) // anybill main navigation graph
                    .setArguments(data.toDeepLinkBundle()).createPendingIntent()
                    .send() // Map data to bundle and navigate to UI Module 
            }
        }
    }
}

// ...

// Handle incoming AppLinks when app was not installed and is opened for the first time
override fun onWindowFocusChanged(hasFocus: Boolean) {
    super.onWindowFocusChanged(hasFocus)
    AnybillAppLink.checkForAnybillAppLinkData(this) { data ->
        if (data != null) {
            navController.createDeepLink()
                .setGraph(R.navigation.nav_graph_main) // Your main navigation graph
                .setDestination(R.id.anybill_nav_graph) // anybill main navigation graph
                .setArguments(data.toDeepLinkBundle()).createPendingIntent()
                .send() // Map data to bundle and navigate to UI Module 
        }
    }
}


# Open receipt directly from Push Notification

To open the UI Module on new notifications catch the incoming notification in your MainActivity:


// Handle incoming notifications when app was not in background
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    //...
    checkForBillIdDeepLink(intent)
}


// ...

// Handle incoming notifications when app was in background
override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    intent?.run {
        checkForBillIdDeepLink(this)
    }
}

/**
 * Checks if intent includes an ID of a new receipt and created
 *
 * @param intent Current Intent
 */


private fun checkForBillIdDeepLink(intent: Intent) {
    //Check if bundle includes values "type" and "billId"
    AnybillURLUtils.checkForAnybillBillId(intent)?.let { billId ->
       navController.createDeepLink()
            .setGraph(R.navigation.nav_graph_main) // Your main navigation graph
            .setDestination(R.id.anybill_nav_graph) // anybill main navigation graph
            .setArguments(data.toDeepLinkBundle()).createPendingIntent()
            .send() // Map data to bundle and navigate to UI Module
    }
}


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

# AnybillStoreUI

The Store UI module provides a UI implementation of the store module by providing a searchable list of stores which have the digital receipt enabled. Detailed information of a store can be displayed. This module requires the UI module to work.

# Adding the store 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:storeui:<VERSION>'
    implementation 'de.anybill.anybill_android_sdk:ui:<VERSION>'

Back to top