# Flutter Integration

# General Information

Getting Started
Anybill App Link

# Base Module

Authentification
User functions
Receipt functions

# Additional Modules

AnybillStore
AnybillContentArea

# Getting Started

# Resolving the SDK:

To include the anybill SDK in your Flutter project, use the dart pub token add command to add your Artifactory access token to the pub tool.

To obtain the access token login in the anybill Artifactory portal (opens new window) into your artifactory account using the provided credentials in the integration documents.

Using the "Set Me Up" Button in the artifact tree, the token can be generated using the included password.



dart-define



dart pub token add https://anybill.jfrog.io/artifactory/api/pub/anybill_flutter_sdk/
  1. Add the desired anybill modules to your app's pubspec.yaml.

dependencies:
  ...
  anybill_base:
    hosted:
      name: anybill_base
      url: https://anybill.jfrog.io/artifactory/api/pub/anybill_flutter_sdk
    version: ^1.0.0
  ...
  anybill_content_area:
    hosted:
      name: anybill_content_area
      url: https://anybill.jfrog.io/artifactory/api/pub/anybill_flutter_sdk
    version: 1.0.0

TIP

anybill_base contains all base functionalities of the anybill sdk and is required by the additional modules.

# Setting the client Id

Within the provided integration documents you are going find a client ID, which is used to identify the calling application To set the client ID in your app, add the ID as a dart evironment variable called ANYBILL_CLIENT_ID using --dart-define.

flutter run --dart-define="ANYBILL_CLIENT_ID={your_client_id}"

You can add this to your default configuration in VSCode and Android Studio:

VSCode

.vscode/launch.json

{
    "configurations": [
        {
            "name": "YourName",
            "type": "dart",
            "request": "launch",
            "program": "lib/main.dart",
            "flutterMode": "debug",
            "toolArgs": [
                "--dart-define",
                "ANYBILL_CLIENT_ID={your_client_id}",
            ],
        },
    ]
}

Android Studio

Run > Edit Configuration

dart-define

Release Build

Do not forget to add the environment variable to your release build aswell!

# Change the api mode to staging

For developing purposes you can switch the api environment by adding the Staging/Test URL as a evnironment variable for the key ANYBILL_BASE_URL using --dart-define.

flutter run --dart-define="ANYBILL_BASE_URL={desired_environment_url}"

Available Enviroments:

/// URL of the API production environment
"https://app.anybill.de/api"

/// URL of the API staging environment
"https://app.stg.anybill.de/api"

/// URL of the API test environment
"https://app.test.anybill.de/api"

Release Build

Do not use this environment variable during release builds to avoid communicating with an invalid environment!

# Initialize the anybill Logger

The anybill SDK has an integrated logging tool used to log any errors or issues occuring within the sdk itself. Initialize the Logger in your main:


import "package:anybill_base/anybill_base_utils.dart";

void main() {
  AnybillLogger.init();
  runApp(
    MaterialApp(
      ....
  );
}

# Usage of the SDK

# Error Handling

The anybill sdk uses a custom error handling model. All methods return a type of the sealed class AnybillResult 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.

/// Object for displaying the results when accessing the API.
class AnybillResult<T> {

  /// Used when the result of the API has been successfully returned.
  factory AnybillResult.success(
    /// The HTTP status code as integer.
    int code, {
    /// The results' data. Can be null.
    T? data,
  }) = Success<T>;

  /// Used when the result of the API has failed while being retrieved.
  factory AnybillResult.failure({
    /// Type of the error (see [AnybillErrorType])
    required AnybillErrorType type,

    /// The HTTP status code as integer.
    int? code,

    /// Optional error message
    String? message,
  }) = Failure;
}

For Success<T> the sdk mostly differs between Http code 200 and 204, while results with code 200 include the returned object of type T in their data parameter.

Result objects of type Failure include an additional custom error type AnybillErrorType. The most common ones are going to be AnybillErrorType.genericError and AnybillErrorType.networkError with GenericError representing an error during a API Call with codes between 400 and 499 and NetworkError including all network related errors like timeouts or server errors with codes > 500.

Additionally the sdk contains custom error types:


/// Enum class for the error types that can be returned from the API.
enum AnybillErrorType {
  /// This error type is used for generic errors that do not fit into any other category.
  genericError,

  /// This error type is used when there is a network error while making a request.
  networkError,

  /// This error type is used when there is an issue with refreshing the user's access token.
  invalidRefreshTokenError,

  /// This error type is used when there is an issue with the user's access token.
  invalidAccessTokenError,

  /// This error type is used when there is no user logged in.
  noUserError,

  /// This error type is used when the client ID is not set as an environment variable.
  clientIdNotAvailable,

  /// This error type is used when the issue can't be defined. Typically a critcal error.
  unknown,
}

Example usage of the error model based on the user info method of the AuthProvider.


import "package:anybill_base/anybill_base_models.dart";
import "package:anybill_base/anybill_base_providers.dart";
import "package:anybill_base/anybill_base_utils.dart";

...

Future<void> getAnybillUserInfo() async {

    final userInfoCall = await AuthProvider.instance.getUserInformation();

    if (userInfoCall is Success<UserInformationDto>) {
        // Use `result.data` 
    } else if (userInfoCall is Failure<UserInformationDto>) {
        switch (userInfoCall.type) {
            case AnybillErrorType.genericError: {
                // Display error message
            }
            case AnybillErrorType.networkError: {
                // Display error message
            }
            case AnybillErrorType.noUserError: {
                // Display error message
            }
            ...
            default: {
                // Display error message
            }
        }
    }
}

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:

Android


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

iOS

Enable the capability Associated Domains and register the domain "applinks:applink.anybill.de".

To associate your app with our website, you'll have to provide a Digital Asset Link file for Android and your iOS identifier including your iOS Developer Team ID.

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


WARNING

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 order to handle incoming deep links it is recommended to use the AppLinkHandler from our SDK.

To ensure that only the correct url is parsed you have to pass your sub path name to the constructor of the class (e.g.: AppLinkHandler("sub-path-name")). If the app was installed via the redirect link from our receipt website the information needed to add the bill is persisted and you can call the checkFirstRunForAppLink() method on app start to retrieve the information in an AppLinkData object.

!!! Note that the code is only executed if it is the first app start. !!!

If the user opens the already installed app via an app link you can use the handleAppLink(String? stringLink) method to parse the incoming url string to an AppLinkData object with the necessary information to add the bill to the user account.

An easy way to listen for incoming app links is the app_links dart library (opens new window).

E.g.:


import "package:anybill_base/anybill_base_utils.dart";
import "package:app_links/app_links.dart";

...

final _appLinkHandler = AppLinkHandler("sub-path-name");

void initAppLinkListener() {
  AnybillLogger.debug(message: "Listening to app link stream");
  AppLinks().allStringLinkStream.listen(
    (link) {
      _appLinkHandler.handleAppLink(link).then((billData) {
        if (billData != null) {
          // Add bill to user account
        }
      });
    },
    onError: (exception) {
      AnybillLogger.error(
        library: "AppLinkHandler",
        event: "initAppLinkListener",
        error: exception,
      );
    },
  );
}

Future<void> main() async {
  ...
   _appLinkHandler.checkFirstRunForAppLink().then((billData) {
      if (billData != null) {
        // Add bill to user account
      }
  });
  initAppLinkListener()
  runApp(YourApp());
}

# Authentification

The AuthProvider contains authentication functions used in the anybill sdk to authenticate a user and access user information.

# Login with Token

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. For this you'll need a backend integration to retrieve token information for the linked user account using the Partner Platform API and provide these to the anybill SDK.

Simplified steps to authenticate a linked user account:

  • Get a token from the anybill Partner Platform API by linking you account system to an anybill id within your backend.
  • Access token from your app using an Endpoint of your backend.
  • Provide token to anybill SDK using `AuthProvider.instance.login

import "package:anybill_base/anybill_base_models.dart";
import "package:anybill_base/anybill_base_providers.dart";
import "package:anybill_base/anybill_base_utils.dart";

...

Future<void> loginAnybillUser() async {

    final yourBackendCall = await YourBackendService.instance.getAnybilLTokenForUser();


    final loginCall = await AuthProvider.instance.loginUserWithToken(
        accessToken: yourBackendCall.accessToken,
        refreshToken: yourBackendCall.refreshToken,
        expiresIn: yourBackendCall.expiresIn,
    )

    if (loginCall is Success<void>) {
        // Logged in successfully
    } else if (loginCall is Failure) {
        // Failed to login
    }
}

TIP

The provided Access Token is valid for 24 hours. The anybill SDK handles the refresh of the Access Token internally as long as the Refresh Token is valid. The Refresh Token expires after 90 days. This might occurr when the app hasn't been opened for 90 days or the token has been invalidated from another client.

When using an linked user account you'll have to check for a failing Refresh Token Call of the anybill SDK on every API call you invoke. When the error is triggered you'll have to retrieve a new token from the anybill Partner Platform API for the linked user account.


import "package:anybill_base/anybill_base_models.dart";
import "package:anybill_base/anybill_base_providers.dart";
import "package:anybill_base/anybill_base_utils.dart";

...

Future<void> getAnybillUserInfo() async {

    final userInfoCall = await AuthProvider.instance.getUserInformation();

    if (userInfoCall is Success<UserInformationDto>) {
        // Use `result.data` 
    } else if (userInfoCall is Failure<UserInformationDto>) {
        switch (userInfoCall.type) {
            case AnybillErrorType.genericError: {
                // Display error message
            }
            case AnybillErrorType.invalidRefreshTokenError: {
                await loginAnybillUser();
            }
            ...
            default: {
                // Display error message
            }
        }
    }
}

Future<void> loginAnybillUser() async {

    final yourBackendCall = await YourBackendService.instance.getAnybillTokenForUser();


    final loginCall = await AuthProvider.instance.loginUserWithToken(
        accessToken: yourBackendCall.accessToken,
        refreshToken: yourBackendCall.refreshToken,
        expiresIn: yourBackendCall.expiresIn,
    )

    if (loginCall is Success<void>) {
        // Logged in successfully
    } else if (loginCall is Failure) {
        // Failed to login
    }
}


Further information about the login possibilities can be found on the anybill sdk overview.

# Login with Credentials

Alternatively to the login with token you can login a user with e-mail and password.

    
import "package:anybill_base/anybill_base_models.dart";
import "package:anybill_base/anybill_base_providers.dart";
import "package:anybill_base/anybill_base_utils.dart";

...

Future<void> loginAnybillUser({
    /// Email of the user
    required String email,

    /// Password of the user
    required String password,
}) async {

    final loginCall = await AuthProvider.instance.loginUser();

    if (loginCall is Success<void>) {
        // Logged in successfully
    } else if (loginCall is Failure) {
        // Failed to log in
    }
}


# Register

In order to register a new anybill user, you need to go through our registration flow. The flow conststs of preregistering the user. After the endpoint excecution an e-mail is sent out to the given user mail with a verification code.

    
import "package:anybill_base/anybill_base_models.dart";
import "package:anybill_base/anybill_base_providers.dart";
import "package:anybill_base/anybill_base_utils.dart";

...

Future<void> preregisterAnybillUser({
    /// Firstname of the user
    required String firstname,

    /// Email of the user
    required String email,
}) async {

    final preregisterCall = await AuthProvider.instance.preregisterUser(
            email: email,
            firstname: firstname,
          );


    if (preregisterCall is Success<void>) {
        // Preregister successfully
    } else if (preregisterCall is Failure) {
        // Failed to preregister
    }
}

With the code verification endpoint you can check if a given code is valid before excecuting the endpoint to finalize the registration. Note that this step is optional.

    
import "package:anybill_base/anybill_base_models.dart";
import "package:anybill_base/anybill_base_providers.dart";
import "package:anybill_base/anybill_base_utils.dart";

...

Future<void> validateRegistrationCode({
    /// Registration code of the user
    required int code,

    /// Email of the user
    required String email,
}) async {

    final codeValidationCall = await AuthProvider.instance.validateRegistrationCode(
            email: email,
            code: code,
          );


    if (codeValidationCall is Success<void>) {
        // Code is valid
    } else if (codeValidationCall is Failure) {
        // Code is invalid or excecution failed
    }
}

The last step of the flow is to finalize the registration.

    
import "package:anybill_base/anybill_base_models.dart";
import "package:anybill_base/anybill_base_providers.dart";
import "package:anybill_base/anybill_base_utils.dart";

...

Future<void> finalizeRegistration({
    /// Registration code of the user
    required int code,

    /// Email of the user
    required String email,

    /// Password of the user
    required String password,

    /// Flag if user should be logged in after registration (default: true)
    bool autoLogin,
}) async {

    final registerCall = await AuthProvider.instance.finalizeRegistration(
            email: email,
            code: code,
            password: password,
            autoLogin: autoLogin,
          );


    if (registerCall is Success<void>) {
        // Registration success
    } else if (registerCall is Failure) {
        // Code is invalid or excecution failed
    }
}

# Create anonymous user

With the createAnonymousUser() method, you can create an anonymous user account without an email or password. The account can use the anybill services with certain restrictions. Later on the account can be converted to a normal anybill account with email, password and further user information.

    
import "package:anybill_base/anybill_base_models.dart";
import "package:anybill_base/anybill_base_providers.dart";
import "package:anybill_base/anybill_base_utils.dart";

...

Future<void> loginAnonymous() async {

    final result = await AuthProvider.instance.createAnonymousUser()


   if (result is Success<String>) {
        /// Created anonymous user. Result contains the userId  
    } else if (result is Failure<String>) {
        /// Failed to create anonymous user
    }
}

# Forgot password

You can direct the user to the anybill forgot password page, by excecuting the constructForgotPasswordUrl() method and linking to the returned uri.

The example below uses the url launcher library to open the forgot password uri inside the application.

import "package:anybill_base/anybill_base_providers.dart";
import "package:url_launcher/url_launcher.dart";

Future<void> openForgotPasswordWebView() async {

    final result = AuthProvider.instance.constructForgotPasswordUrl();
    await _launchUrl(result);
}

  Future<void> _launchUrl(Uri url) async {
    if (!await launchUrl(url)) {
      throw Exception("Could not launch $url");
    }
  }

# Logout

Logging out an user deletes all of user's app data including cached bills, authentication information and app settings (of the anybill sdk).

    
import "package:anybill_base/anybill_base_models.dart";
import "package:anybill_base/anybill_base_providers.dart";
import "package:anybill_base/anybill_base_utils.dart";

...

Future<void> logoutAnybillUser() async {

    final logoutCall = await AuthProvider.instance.logoutUser();

    if (logoutCall is Success<void>) {
        // Logged out successfully
    } else if (loginCall is Failure) {
        // Failed to log out
    }
}


# TokenProvider

WARNING

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

# User functions

Besides the authentication methods the AuthProvider also contains methods to retrieve user related information.

# Access user information

The user information can be fetched using the getUserInformation() method in the AuthProvider. The returned model contains the userId as well as other personal information about the user. When fetched, the user information is stored in the local database and can then be retrieved with the getCachedUserInformation() method of the same class. Further information about the caching can be found in the offline availability section.

    
import "package:anybill_base/anybill_base_models.dart";
import "package:anybill_base/anybill_base_providers.dart";
import "package:anybill_base/anybill_base_utils.dart";

...

Future<void> getUserInformation() async {

    final userInformationCall = await AuthProvider.instance.getUserInformation();

    if (userInformationCall is Success<UserInformationDto>) {
        final userInformation = userInformationCall.data;
        // Successfully fetched user information model
    } else if (userInformationCall is Failure<UserInformationDto>) {
        // Failed to get user information
    }
}


# Access user QR code

Certain points of sale allow anybill users to receive bills by scanning a QR code on the user's phone. The user qr code data can be retrieved using the getUserQrCode() method inside the AuthProvider. Similar to the user information the qr code data is stored in the local database when it is retrieved from the anybill api. The cached qr code data is accessable with the getCachedUserQrCode() method. The simplest way to display the user qr code is to use an external library. In the example below we used the qr_flutter (opens new window) library.

    
import "package:anybill_base/anybill_base_models.dart";
import "package:anybill_base/anybill_base_providers.dart";
import "package:anybill_base/anybill_base_utils.dart";

import 'package:qr_flutter/qr_flutter.dart';
import 'dart:convert';

...

Future<UserQrCodeDto?> _getUserQrCode() async {

    final userQrCodeCall = await AuthProvider.instance.getUserQrCode();

    if (userQrCodeCall is Success<UserQrCodeDto>) {
        return userQrCodeCall.data;
    }

    final cachedUserQrCodeCall = await AuthProvider.instance.getCachedUserQrCode();

    if (cachedUserQrCodeCall is Success<UserQrCodeDto>) {
        return cachedUserQrCodeCall.data;
    }

    return null;
        
}

...

// Inside the widget tree

FutureBuilder<UserQrCodeDto?>(
    future: _getUserQrCode(),
    builder: (context, snapshot) {
        final qrData = snapshot.data;
        if (qrData != null) {
            return QrImageView(data: 
                json.encode(qrData.toJson()),
            );
        }
        // Empty return if call failes and user qr code data is not cached
        return SizedBox.shrink()
    },
)

# Offline availability

The anybill SDK includes a local store of user data to provide offline availability and efficient displaying of data. Mostly the associated API methods are used to update the data in the local store. By using this approach, the data is first accessed from the local store, which is faster than making an API call. If the data is already available locally, the app can show the data quickly without waiting for the API call to complete. If the data is not available locally, the app makes an API call asynchronously and updates the UI once the data is retrieved. This ensures efficient and smooth user experience and offline availability.

E.g.:

// Get user information from anybill Backend and update in local storage
AuthProvider.instance.getUserInformation()

// Get user information fron local store
AuthProvider.instance.getCachedUserInformation()

An example implemtation using Bloc could look like this:


import 'dart:async';

import 'package:anybill_base/anybill_base_models.dart';
import 'package:anybill_base/anybill_base_providers.dart';
import 'package:anybill_base/anybill_base_utils.dart';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';

...

class UserBloc extends Bloc<UserEvent, UserState> {
    UserBloc () : super(UserInitial()) {
        on<UpdateUserInformationEvent>(_fetchUserInformation);
    }
    UserInformationBloc() : super(YourInitialState());

    // Method to fetch user information
    FutureOr<void> _fetchUserInformation(
        UpdateReceiptsEvent event,
        Emitter<ReceiptState> emit,
    ) async {
        try {
            // Show loading state
            emit(YLoadingState());

            // First, try to get user information from the local store
            final cachedResult = await AuthProvider.instance.getCachedUserInformation();

            if (cachedResult is Success<UserInformationDto>) {
                // If user information is available in the local store, emit the loaded state with the data
                emit(LoadedState(cachedResult.data));
            }

            // If user information is not available in the local store or you want to update the cached information, make the API call
            final apiResult = await AuthProvider.instance.getUserInformation();

            if (apiResult is Success<UserInformationDto>) {
                // If API call is successful, emit the loaded state with the data
                emit(LoadedState(cachedResult.data));
            } else if (apiResult is Failure<UserInformationDto>) {
                switch (apiResult.type) {
                    case AnybillErrorType.genericError: {
                        emit(FailureState(error: "Unable to fetch data from API"));
                    }
                    case AnybillErrorType.networkError: {
                        emit(FailureState(error: "Unable to fetch data from API due to network issues"));
                    }
                    case AnybillErrorType.noUserError: {
                        emit(FailureState(error: "No logged in anybill user"));
                    }
                    ...
                    default: {
                        emit(FailureState(error: "Unable to fetch data from API"));
                    }
                }
            }
        } catch (e) {
            emit(FailureState(error: "$e"));
        }
    }
}


# Receipt functions

Singleton BillProvider grants access to the anybill receipt functions.

# 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 multiple methods to retrieve the receipts of a logged in user:


// Get receipts from anybill backend. Parameters allow a custom implementation for pagination and caching
BillProvider.instance.getBills(int skip, int take, String changesSince)

// Gets cached receipts which can be used for offline availability or efficient displaying of data
BillProvider.instance.getCachedBills()

// Updates the cached receipts using the anybill caching algorithm
BillProvider.instance.updateBills()

TIP

To efficiently fetch and display the receipts a combination of getCachedBills() and updateBills() is recommanded.

Example implementation:


import 'dart:async';

import 'package:anybill_base/anybill_base_models.dart';
import 'package:anybill_base/anybill_base_providers.dart';
import 'package:anybill_base/anybill_base_utils.dart';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';

...

class ReceiptBloc extends Bloc<ReceiptsEvent, ReceiptState> {
  ReceiptBloc() : super(ReceiptInitial()) {
    on<UpdateReceiptsEvent>(_updateReceiptData);
  }

  Future<FutureOr<void>> _updateReceiptData(
    UpdateReceiptsEvent event,
    Emitter<ReceiptState> emit,
  ) async {
    try {
      emit(LoadingState());

      final cachedResult = await BillProvider.instance.getCachedBills();

      if (cachedResult is Success<List<BillBaseDto>> &&
          cachedResult.data != null) {
        emit(LoadedState(data: cachedResult.data));
      }

      final apiResult = await BillProvider.instance.updateBills();

      if (apiResult is Success<List<BillBaseDto>> &&
          apiResult.data != null) {
        emit(LoadedState(data: apiResult.data));
      } else if (apiResult is Failure<List<BillBaseDto>>) {
        switch (apiResult.type) {
          case AnybillErrorType.genericError:
            {
              emit(FailureState(
                errorMessage: "Unable to fetch data from API",
              ));
            }
          case AnybillErrorType.networkError:
            {
              emit(FailureState(
                errorMessage:
                    "Unable to fetch data from API due to network issues",
              ));
            }
          case AnybillErrorType.noUserError:
            {
              emit(FailureState(
                errorMessage: "No logged in anybill user",
              ));
            }
          default:
            {
              emit(FailureState(
                errorMessage: "Unable to fetch data from API",
              ));
            }
        }
      }
    } catch (e) {
      emit(FailureState(errorMessage: "$e"));
    }
  }
}

# Retrieving bills as stream

Additionaly the anybill SDK provides a Stream of the cached receipts which can be used to listen to changes triggered by updateBills().


Stream<List<BillBaseDto>>> _receiptStream;

Future<void> getCachedBillStream() async {

    final result = await BillProvider.instance.getCachedBillsAsStream();

    if (result is Success<Stream<List<BillBaseDto>>>) {
        _receiptStream = result.data;
    } else if (result is Failure<Stream<List<BillBaseDto>>>) {
        // Display error message
    }

}

In Widget Tree:


StreamBuilder(
    stream: _receiptStream,
    builder: (context, snapshot) {
        // Display receipts in $snapshot.data as list
    },
),

Update the data using BillProvider.updateBills():



Future<void> updateReceiptData() async {

    final result = await BillProvider.instance.updateBills();

    if (result is Failure<List<BillBaseDto>>) {
        // Display error message
    }

}


WARNING

To avoid memory leaks do not forget to cancel the stream using _receiptStream.cancel().

# Export receipt as PDF

The receipt data included in the BillDto object represents the original receipt which is stored as a PDF file in the anybill backend. To obtain the original receipt and share or save the receipt as PDF, the anybill SDK provides a method to receive the PDF data from the anybill API.

The method BillProvider.instance.getBillAsPDF() generates a PDF file of the receipt and returns a List of bytes (List<int>) which can be converted to a PDF file locally.



import "package:anybill_base/anybill_base_models.dart";
import "package:anybill_base/anybill_base_providers.dart";
import "package:anybill_base/anybill_base_utils.dart";

...

Future<void> getBillAsPDF() async {

    final pdfCall = await BillProvider.instance.getBillAsPdf(billId: "fce03754-4209-4e50-a3f9-9141989c726e");

    if (pdfCall is Success<List<int>>) {
        final pdfData = Uint8List.fromList(result.data);
        // Display pdfData using a PDF View Library or share to other apps 
    } else if (pdfCall is Failure<List<int>>) {
        // Failed to export receipt
    }
}

# Add receipt to user account

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. Using the AnybillURLUtils the extracted string can be parsed into a AnybillURLData. If a valid AnybillURLData object could be parsed, forward the extracted information to the anybill SDK using BillProvider.instance.addBillbyId() to add the receipt to the users account.

To avoid spamming API calls during scanning of QR Codes, always check for a valid AnybillURLData object and reduce the amount of paralell API Calls to 1.

  //Depending on the QR Code Scanner you are going to receive a String or Uri 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
  //Example implementation

Future<void> addReceiptFromQrScan(String urlFromQRScan) async {
  final anybillUrlData = AnybillURLUtils.extractAnybillURLData(urlFromQRScan);

  if (anybillUrlData != null) {
    final result = await BillProvider.instance.addBillById(
      billId: anybillUrlData.billId,
      vendorCustomerId: anybillUrlData.vendorCustomerId,
      isSelfGenerated: anybillUrlData.isSelfGenerated,
    );

    if (result is Success<AddBillResponseDto>) {
      if (result.data?.isPreGeneratedBill == true) {
        // Display information that receipts is going to be added after
        // payment process is finished
      } else {
        // Display receipt with id result.data?.id
      }
    } else if (result is Failure<AddBillResponseDto>) {
      switch (result.type) {
        case AnybillErrorType.genericError: {
          // Display error message
            if (result.code == HttpStatus.notFound) {
              // Check whether receipt with id anybillUrlData.billId is already in users receipts
            }
        }
        case AnybillErrorType.networkError: {
          // Display error message
        }
        case AnybillErrorType.noUserError: {
          // Display error message
        }
        ...
        default: {
          // Display error message
        }
      }
    }
  } else { 
    // Invalid URL
  }
}

# Delete receipts

Deleting a receipt is irreversible. After deleting receipts with BillProvider.deleteBill(bill: Bill) the cached receipts should be updated using BillProvider.instance.updateBills().


import "package:anybill_base/anybill_base_models.dart";
import "package:anybill_base/anybill_base_providers.dart";
import "package:anybill_base/anybill_base_utils.dart";

...

Future<void> deleteReceipts(List<Strings> billIds) async {

    final deleteCall = await BillProvider.instance.deleteBills(billIds: billIds);

    if (deleteCall is Success<void>) {
        // Successfully deleted receipts from user account
        // Update list using `BillProvider.instance.updateBills();

    } else if (deleteCall is Failure<void>) {
        // Failed to delete receipts
    }
}

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.

# Include anybill_store package

The anybill_store package requires the anybill_base package to be included in the dependencies of the app.


dependencies:
  ...
  anybill_base:
    hosted:
      name: anybill_base
      url: https://anybill.jfrog.io/artifactory/api/pub/anybill_flutter_sdk
    version: ^1.0.0
  ...
  anybill_store:
    hosted:
      name: anybill_store
      url: https://anybill.jfrog.io/artifactory/api/pub/anybill_flutter_sdk
    version: ^1.0.0

# Retrieving stores

Similiar to the BillProvider the StoreProvider provides 3 methods to fetch stores:


// Get stores from anybill backend. Parameters allow a custom implementation for pagination and caching
StoreProvider.instance.getStores(int skip, int take, String changesSince)

// Gets cached stores which can be used for offline availability or efficient displaying of data
StoreProvider.instance.getCachedStores()

// Updates the cached stores using the anybill caching algorithm
StoreProvider.instance.updateStores()

TIP

To efficiently fetch and display the stores a combination of getCachedStores() and updateStores() is recommanded.

Example implemetation of a Bloc using the caching process can be found in the receipt functions documentation.

# Retrieving store details

The AnybillStoreDto data model does not return all available information about a store. Some properties of a store have to be queried separately via the getStoreDetails(String storeId) method.

Sample-code for your viewmodel:


import "package:anybill_base/anybill_base_models.dart";
import "package:anybill_base/anybill_base_utils.dart";
import "package:anybill_store/anybill_store_providers.dart";

...

Future<void> getStoreDetails(AnybillStoreDto store) async {

    final storeDetailsCall = await StoreProvider.instance.getStoreDetails(store.id);

    if (storeDetailsCall is Success<AnybillStoreDetailsDto>) {
        // Display store information
    } else if (storeDetailsCall is Failure<AnybillStoreDetailsDto>) {
        // Failed to fetch store details
    }
}

TIP

Equivalent to other data model the store details are cached (once fetched) and are available by StoreProvider.instance.getStoreDetails().

# AnybillContentArea

# Include anybill_content_area package

The anybill_content_area package requires the anybill_base package to be included in the dependencies of the app.


dependencies:
  ...
  anybill_base:
    hosted:
      name: anybill_base
      url: https://anybill.jfrog.io/artifactory/api/pub/anybill_flutter_sdk
    version: ^1.0.0
  ...
  anybill_content_area:
    hosted:
      name: anybill_content_area
      url: https://anybill.jfrog.io/artifactory/api/pub/anybill_flutter_sdk
    version: ^1.0.0

# Retrieve ContentArea

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.

Instances of ContentArea can be retrieved through the ContentAreaProvider. This Provider exposes the method getContentAreaByBillId(billId: String), returning a ContentAreaDto.


import "package:anybill_base/anybill_base_models.dart";
import "package:anybill_base/anybill_base_utils.dart";
import "package:anybill_content_area/anybill_content_area_providers.dart";
...

String? _contentAreaResourceUri;

Future<void> getContentArea(BillBaseDto bill) async {

    final contentAreaCall = await ContentAreaProvider.instance.getContentAreaByBillId(billId: bill.id);

    if (contentAreaCall is Success<ContentAreaDto>) {
        // Display the generated Resource Uri of the ContentAreaDto
        _contentAreaResourceUri = contentAreaCall.data.generatedResourceUri;
    } else if (contentAreaCallis Failure<ContentAreaDto>) {
        // Failed to fetch content area or no content area available
    }
}

In Widget Tree:


  Image.network(_contentAreaResourceUri);

Back to top