Android SDK Integration

Introduction

The Fintech Bridge SDK, com.subaio.fintechbridge is intended for embedding small, widget-like applications inside a larger application. The full application can be considered as the following parts:

Including embedded applications thus requires including the bridge library, configuring the BridgeManager and instantiating a BridgeView.

The Fintech Bridge supports Android SDK 21 and up.

Prerequisites

Installation

The Android SDK is versioned using semver 2.0.0 without any prefix. Thus, patch and minor versions should require no changes. We recommend always using the latest version if possible.

The SDK has a single external dependency, Trustkit, used to handle SSL pinning in Android SDK 21-23.

The SDK can be installed either using Maven or manually.

Using Gradle/Maven

Having added the Maven repository that mirrors the Subaio Maven repository, add the following to the build.gradle-file under the dependencies section:

implementation 'com.subaio.fintechbridge:fintechbridge:<version>'

The SDK and its dependency should be automatically installed when syncing the gradle project.

Manually installation

Access the .jar-, .aar- and .pom files, either directly from the Subaio Nexus instance, or from a provided zip-archive. Do the same for Trustkit from https://repo.spring.io/libs-milestone/com/datatheorem/android/trustkit/trustkit/1.1.0/.

Place these files somewhere inside your project. Add the following to the build.gradle-file under the dependencies section:

implementation files('<path to file>/trustkit-1.1.0.aar')
implementation files('<path to file>/fintechbridge-<version>.aar')

The SDK and its dependency should be automatically added when syncing the gradle project.

BridgeManager

The BridgeManager is used to configure shared settings and manage authentication. It can either be used as a singleton, BridgeManager.getInstance(), or it can be instantiated and passed to all BridgeViews manually.

Configuration

Before any views can be instantiated, the BridgeManager needs to be configured. Configuration is passive, and nothing happens until a BridgeView is created, so it can be done during application startup.

The following options are required:

Optionally, the following may be set:

These can be set through a fluent interface by calling startConfiguration and ending with apply.

Example configuration:

BridgeManager.getInstance()
    .startConfiguration()
    .setBaseUrl(baseUrl)
    .setLanguage(language)
    .setCustomConfig(customConfig)
    .setAllowExternalUrls(true)
    .setAllowShare(true)
    .setRequestHandler(new TrustKitRequestHandler(this /* Android context */))
    .setTokenHandler(...)
    .apply()

Authentication

The BridgeViews use JSON web tokens for authentication. All views use a shared token, administred by the BridgeManager. The tokens generally expire after one hour, so the BridgeManager has internal logic to automatically refresh the token when necessary.

The preferred method of obtaining a subaio token is to let the host application make an authenticated request to its own backend, which will then request a token from the subaio backend.

The host application is responsible for providing a callback for obtaining a token to the BridgeManager. This callback will be passed a handler class, where exactly one of the following methods should be called:

The current user language/locale should generally be passed along to the subaio backend during login, so as a convenience it is also passed along to the callback.

Example implementation:

new BridgeManager.TokenHandler() {
    @Override
    public void onTokenRequired(String language, BridgeManager.TokenResultHandler resultHandler) {
        if (/* User is unauthenticated */) {
            resultHandler.unauthenticated();
            return;
        }
        try {
            String token = /* Fetch token from backend */;
            resulthandler.success(token);
        } catch (Exception e) {
            resultHandler.unavailable();
        }
    }
}

If the user is logged out or a new user is logged in, the host appliation should call BridgeManager::refreshToken, to ensure there is no lingering authentication token.

The BridgeManager will invoke the onTokenRequired callback when:

During initial implementation, before exchanging public-keys, we’ll generally generate a temporary private-key ourselves for testing and use this for unauthenticated logins. If necessary, this can be made available to you as well.

SSL pinning

SSL pinning limits the domains available in the webviews and ensures you connect to the correct servers. On Android SDK 21-23, where native SSL pinning is unsupported, SSL pinning can be handled by using Trustkit.

If native SSL pinning is sufficient, setRequestHandler should be provided with a NativeRequestHandler. If SSL-pinning is required in Android SDK 21-23, TrustKitRequestHandler should be used.

Configuration is done through Network Security Configuration. A base64 hash of the certificate public key is used to identify it. The following certificate hashes are used:

Subdomain Hash Expiration Notes
*.integration.subaio.com PIVXah13Vm1r1r6NasA8bQLtXeoa9d/JyiKNvixhClE= 2021-09-15 Unstable, automatically renewed periodically.
*.qa.subaio.com cwiT9CwW75Y59Dphqf9rzPF+J31AWN2weIWzhAUiJgo= 2022-02-21 Stable, Subaio coordinates renewal well before expiration.
*.prod.subaio.com CHPyrl5NSheizV81padL1pM4+TciQd4MJo2VRV7BzFc= 2022-02-21 Stable, Subaio coordinates renewal well before expiration. Should always be included, since error logging is hosted here.

Each subdomain can have multiple hashes defined without problems, so near expiration a new hash is simply added alongside the others.

The hashes should be added to the domain-config section of the Network Security Configuration for subaio.com subdomains. In addition, a trustkit-config section can be added. Example configuration:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">subaio.com</domain>
        <pin-set>
            <!-- *.qa.subaio.com -->
            <pin digest="SHA-256">PPqSMfht94cpmovcJ+bBEupcmKvGxPReeNoWsJL0Gok=</pin>
            <!-- *.prod.subaio.com -->
            <pin digest="SHA-256">PO7y+PL8YCAcEIZzhekzoFgkx92aIB6HfHzanC7RDNI=</pin>
        </pin-set>
        <trustkit-config enforcePinning="true" disableDefaultReportUri="true">
        </trustkit-config>
    </domain-config>
</network-security-config>

Language

Language is defined using a BCP 47 language tag, such as en or da-DK. The current language can be changed at any time, immediately to be reflected in all open views. The fallback language when an unknown language is english, but this can be changed if necessary.

Sharing and external URLs

To better allow fine-grained control of your application, these events can be disabled. Generally, they should be enabled unless otherwise agreed. They are used to open subscription overviews in subscription aggregators, such as iTunes or Google Play.

Custom configuration (optional)

If necessary, custom configuration values can be sent to the webview. This is useful if the application allows for in-app theme changes or as a potention channel to share user information with the embedded application without passing it through backend servers.

Note that custom configuration is only necessary for dynamic theming that can only be known at runtime. General theming should be decided as part of the design process.

customConfig is added to the SubaioConfiguration as an optional string-dictionary:

Example:

final Map<String, String> customConfig = new HashMap<>();
customConfig.put("mode", "light");

BridgeManager.getInstance()
    .startConfiguration()
    // ...
    .setCustomConfig(customConfig)
    // ...
    .apply()
final Map<String, String> customConfig = new HashMap<>();
customConfig.put("mode", "dark");
BridgeManager.getInstance().setCustomConfig(customConfig);

Create a BridgeView

BridgeView is a FrameLayout-wrapper for a WebView to handle authentication, configuration and events from the WebView. The BridgeView should have a Page provided through setPage and a BridgeViewListener with event-handlers provided through setListener.

Example of simple activity wrapping a BridgeView:

public class BridgeViewActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_web);

        Page page = (Page) this.getIntent().getSerializableExtra("page");
        if (page == null) {
            page = Page.overview();
        }

        final BridgeView BridgeView = findViewById(R.id.BridgeView);
        BridgeView.setPage(page);

       bridgeView.setListener(new BridgeView.BridgeViewListener() {
            @Override
            public void onViewNavigated(BridgeView view, Page page) {
                // Open a new activity with the new page
                startActivity(new Intent(getApplicationContext(), BridgeViewActivity.class).putExtra("page", page));
            }

            @Override
            public void onViewDidSelectBack(BridgeView view) {
                // End the activity
                finish();
            }

            @Override
            public void onViewDidReceiveError(BridgeView view, ViewError error) {
                // If ViewError.INTERNAL_ERROR, the view is able to display its own error page, otherwise nothing can be shown, and the host application should take over
            }

            // Optional
            @Override
            public void onViewChangedStatus(BridgeView view, ViewStatus status) {
                // React to the status
            }

            // Optional
            @Override
            public void onViewUpdatedTitle(BridgeView view, String title) {
                // Set toolbar title or similar
            }

            // Optionsl
            @Override
            public void onViewCustomEvent(BridgeView view, String name, Object data) {
                // React to custom events in the embedded application, such as navigation to a view in the host application
            }
        });
    }
}

To better facilitate native transitions between pages, the subaio application is separated in pages that can be placed in separate activities and pushed separately to the nagivation stack.

Each instance of an embedded view can be set to show a specific Page, defined by a name and optionally a set of parameters, e.g. new Page("OVERVIEW", null) or new Page("SUBSCRIPTION", new JSONObject(new HashMap() )). The exact pages will depend on the application. For simple integrations where the views are simply pushed to the navigation stack, the host application only need knowledge of the initial overview page, available as Page.overview() for convenience.

Views will push new pages using the onViewNavigated event.

Notifications for the embedded application should generally be implemented to pass name and params provided by subaio on to the host application to easily handle navigation.

If a common navigation header is used for most activities, it can be used here as well. The BridgeView should be constrained to take up the remaining space. A localized title for the header can be used by the onViewUpdatedTitle event-handler.

Layout

Generally, the BridgeView should simply be sized to take up the full screen, or the remaining space if a navigation header is used.

If, however, the BridgeView should be embedded among existing native content, for instance as a dashboard-widget, a special feature allows scaling the view to automatically match the content. This is triggered by calling view.setAutosizeEnabled(true) on the BridgeView. With autosize, the view will automatically override onMeasure to return the size of the content.

To keep the expected height initially, it is recommended to set a default value for the layout height, agreed upon as part of the design process.