Mini App SDK for Android

Provides functionality to show a Mini App in Android Applications. The SDK offers features like downloading, caching, updating, and displaying of a Mini App. Mini App SDK also facilitates communication between a mini app and the host app via a message bridge.

Table of Contents

Requirements

Getting Started

This section will guide you through setting up the Mini App SDK in your App and you will learn how to display your first mini app.

#1 Add dependency to your App

Add the following to your build.gradle file:

repositories {
    mavenCentral()
}

dependency {
    implementation 'io.github.rakutentech.miniapp:miniapp:${version}'
}

#2 Configure SDK settings in AndroidManifest.xml

The SDK is configured via meta-data tags in your AndroidManifest.xml. The following table lists the configurable values.

Field Datatype Manifest Key Optional Default
Base URL String com.rakuten.tech.mobile.miniapp.BaseUrl 🚫
Is Preview Mode Boolean com.rakuten.tech.mobile.miniapp.IsPreviewMode false
RAS Project ID String com.rakuten.tech.mobile.ras.ProjectId 🚫
RAS Project Subscription Key String com.rakuten.tech.mobile.ras.ProjectSubscriptionKey 🚫
Host App User Agent Info String com.rakuten.tech.mobile.miniapp.HostAppUserAgentInfo 🚫

Note:

Click to expand example AndroidManifest.xml
<manifest>
    <application>

        <!-- Base URL used for retrieving a Mini App -->
        <meta-data
            android:name="com.rakuten.tech.mobile.miniapp.BaseUrl"
            android:value="https://www.example.com" />

        <!-- Preview mode used for retrieving the Mini Apps -->
        <meta-data
            android:name="com.rakuten.tech.mobile.miniapp.IsPreviewMode"
            android:value="${isPreviewMode}" />

        <!-- Project ID for the Platform API -->
        <meta-data
            android:name="com.rakuten.tech.mobile.ras.ProjectId"
            android:value="your_project_id" />

        <!-- Subscription Key for the Platform API -->
        <meta-data
            android:name="com.rakuten.tech.mobile.ras.ProjectSubscriptionKey"
            android:value="your_subscription_key" />

        <!-- Optional User Agent Information relating to the host app -->
        <meta-data
            android:name="com.rakuten.tech.mobile.miniapp.HostAppUserAgentInfo"
            android:value="app_name/version_info" />

    </application>
</manifest>

#3 Create and display a Mini App

API Docs: MiniApp.create, MiniAppDisplay, MiniAppMessageBridge

MiniApp.create is used to create a View for displaying a specific mini app. Before calling MiniApp.create, the Host App should first get the manifest using MiniApp.getMiniAppManifest, show permission prompt to user, then set the result with MiniApp.setCustomPermissions. If Host App wants to launch/download the miniapp without granting the required permissions, the SDK will throw RequiredPermissionsNotGrantedException to notify Host App. You must provide the mini app ID which you wish to create (you can get the mini app ID by Fetching Mini App Info first). Calling MiniApp.create will do the following:

After calling MiniApp.create and all the “required” manifest permissions have been granted, you will obtain an instance of MiniAppDisplay which represents the downloaded mini app. You can call MiniAppDisplay.getMiniAppView to obtain a View for displaying the mini app.

The following is a simplified example:

try {
    val miniAppMessageBridge = object : MiniAppMessageBridge() {
        // implement methods for mini app bridge
    }

    val miniAppDisplay = withContext(Dispatchers.IO) {
        MiniApp.instance().create("MINI_APP_ID", miniAppMessageBridge)
    }
    val miniAppView = miniAppDisplay.getMiniAppView(this@YourActivity)

    // Add the view to your Activity
} catch (e: MiniAppSdkException) {
    // Handle exception
}

Note that this is a simplified example. See Mini App Features for the full functionality which you can provide to the mini app. The following is a more complete example:

Click here to expand a full code example
class MiniAppActivity : Activity(), CoroutineScope {

    override val coroutineContext = Dispatchers.Main

    private var miniAppDisplay: MiniAppDisplay

    override fun onCreate(savedInstanceState: Bundle?) {
        super(savedInstanceState)
        setContentView(R.layout.loading)

        launch {
            try {
                miniAppDisplay = withContext(Dispatchers.IO) {
                    MiniApp.instance().create("MINI_APP_ID", createMessageBridge())
                }
                val miniAppView = miniAppDisplay.getMiniAppView(this@MiniAppActivity)

                setContentView(miniAppView)
            } catch (e: MiniAppSdkException) {
                setContentView(R.layout.error_screen)
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        miniAppDisplay.destroyView()
    }

    fun createMessageBridge() = object : MiniAppMessageBridge() {
        override fun getUniqueId(
            onSuccess: (uniqueId: String) -> Unit,
            onError: (message: String) -> Unit
        ) {
            if (...)
                onSuccess("your-unique-id")
            else
                onError("your-error-message")
        }

        override fun requestPermission(
            miniAppPermissionType: MiniAppPermissionType,
            callback: (isGranted: Boolean) -> Unit
        ) {
            // Implementation details to request device permissions

            callback.invoke(true)
        }
        
        // You can additionally implement other MiniAppMessageBridge methods
    }
}

Note:

Mini App Features

API Docs: MiniAppMessageBridge

The MiniAppMessageBridge is used for passing messages between the Mini App (JavaScript) and the Host App (your native Android App) and vice versa. Your App must provide the implementation for these functions and pass this implementation to the MiniApp.create function.

There are some methods have a default implementation but the host app can override them to fully control.

Method Default
getUniqueId 🚫
requestPermission 🚫
requestDevicePermission 🚫
requestCustomPermissions
shareContent

The UserInfoBridgeDispatcher:

Method Default
getUserName 🚫
getProfilePhoto 🚫
getAccessToken 🚫
getContacts 🚫

The ChatBridgeDispatcher:

Method Default
sendMessageToContact 🚫
sendMessageToContactId 🚫
sendMessageToMultipleContacts 🚫

The sections below explain each feature in more detail.

The following is a full code example of using MiniAppMessageBridge.

Click here to expand full code example of MiniAppMessageBridge
val miniAppMessageBridge = object: MiniAppMessageBridge() {
    override fun getUniqueId() {
        var id: String = ""
        // Implementation details to generate a Unique ID
        // .. .. ..

        return id
    }

    override fun requestDevicePermission(
        miniAppPermissionType: MiniAppDevicePermissionType,
        callback: (isGranted: Boolean) -> Unit
    ) {
        // Implementation details to request device permission for location
        // .. .. ..

        callback.invoke(true)
    }

    // Implement requestCustomPermissions if HostApp wants to show their own UI for managing permissions
    override fun requestCustomPermissions(
        permissionsWithDescription: List<Pair<MiniAppCustomPermissionType, String>>,
        callback: (List<Pair<MiniAppCustomPermissionType, MiniAppCustomPermissionResult>>) -> Unit
    ) {
        // Implementation details to request custom permissions
        // .. .. ..
        // pass a list of Pair of MiniAppCustomPermissionType and MiniAppCustomPermissionResult in callback 
        callback.invoke(list) 
    }

    override fun shareContent(
        content: String,
        callback: (isSuccess: Boolean, message: String?) -> Unit
    ) {
        // Share content implementation.
        // .. .. ..
        
        callback.invoke(true, null) // or callback.invoke(false, "error message")
    }
}

val userInfoBridgeDispatcher = object : UserInfoBridgeDispatcher {

    override fun getUserName(
        onSuccess: (userName: String) -> Unit,
        onError: (message: String) -> Unit
    ) {
        val name: String?
        // Check if there is any valid username in HostApp
        // .. .. ..
        if (isNameValid) // Check if name is valid
            onSuccess(name) // allow miniapp to get user name.
        else
            onError(message) // reject miniapp to get user name with message explanation.
    }

    override fun getProfilePhoto(
        onSuccess: (profilePhoto: String) -> Unit,
        onError: (message: String) -> Unit
    ) {
        val photoUrl: String?
        // Check if there is any valid photo url in HostApp
        // .. .. ..
        if (isPhotoUrlValid) // Check if photoUrl is valid
            onSuccess(photoUrl) // allow miniapp to get photo url.
        else
            onError(message) // reject miniapp to get photo url with message explanation.
    }

    override fun getAccessToken(
        miniAppId: String,
        accessTokenScope: AccessTokenScope,
        onSuccess: (tokenData: TokenData) -> Unit,
        onError: (tokenError: MiniAppAccessTokenError) -> Unit
    ) {
        var allowToken: Boolean = false
        // Check if you want to allow this Mini App ID to use the Access Token based on AccessTokenScope.
        // .. .. ..
        if (allowToken)
            onSuccess(tokenData) // allow miniapp to get token and return TokenData value.
        else
            onError(tokenError) // reject miniapp to get token with specific access token error type.
    }

    override fun getContacts(
        onSuccess: (contacts: ArrayList<Contact>) -> Unit,
        onError: (message: String) -> Unit
    ) {
        // Check if there is any contact id in HostApp
        // .. .. ..
        if (hasContact)
            onSuccess(contacts) // allow miniapp to get contacts.
        else
            onError(message) // reject miniapp to get contacts with message explanation.
    }
}

// set UserInfoBridgeDispatcher object to miniAppMessageBridge
miniAppMessageBridge.setUserInfoBridgeDispatcher(userInfoBridgeDispatcher)

val chatBridgeDispatcher = object : ChatBridgeDispatcher {

    override fun sendMessageToContact(
        message: MessageToContact,
        onSuccess: (contactId: String?) -> Unit,
        onError: (message: String) -> Unit
    ) {
        // Check if there is any contact in HostApp
        // .. .. ..
        if (hasContact) {
            // You can show a contact selection UI for picking a single contact.
            // .. .. ..
            // allow miniapp to invoke after message has been sent,
            // user can invoke null when cancelling the operation.
            onSuccess(contactId)
        }
        else
            onError(message) // reject miniapp to send message with message explanation.
    }

    override fun sendMessageToContactId(
        contactId: String,
        message: MessageToContact,
        onSuccess: (contactId: String?) -> Unit,
        onError: (message: String) -> Unit
    ) {
        if (there is contact id) {
            // You can show a UI with the message content and the contactId.
            // .. .. ..
            // allow miniapp to invoke after message has been sent,
            // user can invoke null when cancelling the operation.
            onSuccess(contactId)
        }
        else
            onError(message) // reject miniapp to send message with message explanation.
    }

    override fun sendMessageToMultipleContacts(
        message: MessageToContact,
        onSuccess: (contactIds: List<String>?) -> Unit,
        onError: (message: String) -> Unit
    ) {
        // Check if there is any contact in HostApp
        // .. .. ..
        if (hasContact) {
            // You can show a contact selection UI for picking a single contact.
            // .. .. ..
            // allow miniapp to invoke the contact ids where message has been sent,
            // user can invoke null when cancelling the operation.
            onSuccess(contactIds)
        }
        else
            onError(message) // reject miniapp to send message with message explanation.
    }
}

// set ChatBridgeDispatcher object to miniAppMessageBridge
miniAppMessageBridge.setChatBridgeDispatcher(chatBridgeDispatcher)

Unique ID

API Docs: MiniAppMessageBridge.getUniqueId

Your App should provide an ID to the mini app which is unique to each user or device. The mini app can use this ID for storing session information for each user.

Device Permission Requests

API Docs: MiniAppMessageBridge.requestPermission

The mini app is able to request some device permissions. Your App should be able to handle requests from the mini app for the following device permissions by ensuring that the Android permission dialog is displayed. Alternatively, if your App is not able to request certain device permissions, you can just deny that permission to all mini apps.

Custom Permission Requests

API Docs: MiniAppMessageBridge.requestCustomPermissions

Mini apps are able to make requests for custom permission types which are defined by the Mini App SDK. These permissions include things like user data. When a mini app requests a permission, your App should display a dialog to the user asking them to accept or deny the permissions. You can also choose to always deny some permissions if your App is not capable of providing that type of data. The following custom permission types are supported:

Note: The Mini App SDK has a default UI built-in for the custom permission dialog, but you can choose to override this and use a custom UI. The Mini App SDK will handle caching the permission accept/deny state, and your requestCustomPermission function will only receive permissions which have not yet been granted to the mini app.

Share Content

API Docs: MiniAppMessageBridge.shareContent

The mini app can share text content to either your App or another App. The default functionality for this will create a text type Intent which shows the Android chooser and allows the user to share the content to any App which accepts text.

You can also choose to override the default functionality and instead share the text content to some feature within your own App.

User Info

API Docs: MiniAppMessageBridge.setUserInfoBridgeDispatcher

The mini app is able to request data about the current user from your App. Each of these types of data is associated with a MiniAppCustomPermissionType (except where noted). The mini app should have requested the permission before requesting the user data type. Note that the Mini App SDK will handle making sure that the permission has been granted, so if the permission has not been granted, then these functions will not be called within your App.

The following user data types are supported. If your App does not support a certain type of data, you do not have to implement the function for it.

Ads Integration

API Docs: MiniAppMessageBridge.setAdMobDisplayer

It is optional to set AdMob for mini apps to show advertisement. The below implementation will allow ads to be shown when mini apps trigger a request.

Configure the Android Ads SDK from here. Don’t forget to initialize the Ads SDK.

Note: We only support AdMob usage on Android 7.0+. Some ads from AdMob have inconsistent behavior on Android 6.0 due to the older webview implementation on those devices.

AdMob

API Docs: AdMobDisplayer

Set the AdMobDisplayer provided by MiniApp SDK. This controller will handle the display of ad so no work is required from host app.

miniAppMessageBridge.setAdMobDisplayer(AdMobDisplayer(activityContext))

Custom Ads Provider

API Docs: MiniAppAdDisplayer

In case the host app wants to take control of the ad display, there is the interface MiniAppAdDisplayer to implement.

class CustomAdDisplayer: MiniAppAdDisplayer { 

    override fun loadInterstitialAd(adUnitId: String, onLoaded: () -> Unit, onFailed: (String) -> Unit) {
      // load the ad
    }
    
    override fun showInterstitialAd(adUnitId: String, onClosed: () -> Unit, onFailed: (String) -> Unit) {
      // show the ad
    }
    //...more ad implementations.
}

miniAppMessageBridge.setAdMobDisplayer(CustomAdDisplayer())

Screen Orientation

API Docs: MiniAppMessageBridge.allowScreenOrientation

The default setting does not allow miniapp to change hostapp screen orientation. Hostapp can allow miniapp to control the screen orientation for better experience by calling

miniAppMessageBridge.allowScreenOrientation(true)

In case miniapp is allowed to control, please ensure that your activity handles screen orientation. There are several ways to prevent the view from being reset. In our Demo App, we set the config on activity android:configChanges="orientation|screenSize". See here.

Fetching Mini App Info

API Docs: MiniApp.listMiniApp, MiniApp.fetchInfo, MiniAppInfo

Information about Mini Apps can be fetched in two different ways: by using MiniApp.listMiniApp to get a list of info for all Mini Apps, or by using MiniApp.fetchInfo to get info for a single Mini App. Either method will return MiniAppInfo objects with info about the Mini App such as name, icon URL, ID, version, etc.

Use MiniApp.listMiniApp if you want a list of all Mini Apps:

CoroutineScope(Dispatchers.IO).launch {
    try {
        val miniAppList = MiniApp.instance().listMiniApp()
    } catch(e: MiniAppSdkException) {
        Log.e("MiniApp", "There was an error retrieving the list", e)
    }
}

Or use MiniApp.fetchInfo if you want info for a single Mini App and already know the Mini App’s ID:

CoroutineScope(Dispatchers.IO).launch {
    try {
        val miniAppInfo = MiniApp.instance().fetchInfo("MINI_APP_ID")
    } catch(e: MiniAppSdkException) {
        Log.e("MiniApp", "There was an error when retrieving the Mini App info", e)
    }
}

Fetching Mini App Meta data

API Docs: MiniApp.getMiniAppManifest

MiniApp developers need to add the following attributes in manifest.json. Host App can require Mini Apps to set meta data here using “customMetaData”, such as the first time launch screen options.

{
   "reqPermissions":[
      {
         "name":"rakuten.miniapp.user.USER_NAME",
         "reason":"Describe your reason here."
      },
      {
         "name":"rakuten.miniapp.user.PROFILE_PHOTO",
         "reason":"Describe your reason here."
      }
   ],
   "optPermissions":[
      {
         "name":"rakuten.miniapp.user.CONTACT_LIST",
         "reason":"Describe your reason here."
      },
      {
         "name":"rakuten.miniapp.device.LOCATION",
         "reason":"Describe your reason here."
      }
   ],
   "customMetaData":{
      "hostAppRandomTestKey":"metadata value"
   }
}

In Host App, we can get the manifest information as following:

CoroutineScope(Dispatchers.IO).launch {
    try {
        val miniAppManifest = MiniApp.instance().getMiniAppManifest("MINI_APP_ID", "VERSION_ID")

        // Host App can set it's own metadata key in manifest.json to retrieve the value
        miniAppManifest.customMetaData["hostAppRandomTestKey"]
    } catch(e: MiniAppSdkException) {
        Log.e("MiniApp", "There was an error when retrieving the Mini App manifest", e)
    }
}

Getting downloaded Mini App Meta data

In Host App, we can get the downloaded manifest information as following:

  val downloadedManifest = MiniApp.instance().getDownloadedManifest("MINI_APP_ID")

Send message to contacts

API Docs: ChatBridgeDispatcher

Send a message to a single contact, multiple contacts or to a specific contact id by using the following three methods can be triggered by the Mini App, and here are the recommended behaviors for each one:

  ChatBridgeDispatcher.sendMessageToContact ChatBridgeDispatcher.sendMessageToContactId ChatBridgeDispatcher.sendMessageToMultipleContacts
Triggered when Mini App wants to send a message to a contact. Triggered when Mini App wants to send a message to a specific contact. Triggered when Mini App wants to send a message to multiple contacts.
Contact chooser needed single contact None multiple contacts
Action send the message to the chosen contact send a message to the specific contact id without any prompt to the user send the message to multiple chosen contacts
On success invoke onSuccess with the ID of the contact where the message was sent. invoke onSuccess with the ID of the contact where the message was sent. invoke onSuccess with a list of IDs of the contacts where the message was sent.
On cancellation invoke onSuccess null value. invoke onSuccess null value. invoke onSuccess null value.
On error invoke onError when there was an error. invoke onError when there was an error. invoke onError when there was an error.

Advanced Features

Clearing up mini app display

API Docs: MiniAppDisplay.destroyView

For a mini app, it is required to destroy necessary view state and any services registered with. The automatic way can be used only if we want to end the Activity container along with mini app display. MiniAppDisplay complies to Android’s LifecycleObserver contract. It is quite easy to setup for automatic clean up of resources.

class MiniAppActivity : Activity(), CoroutineScope {

    override fun onCreate(savedInstanceState: Bundle?) {
    //...
        launch {
            val miniAppDisplay = withContext(Dispatchers.IO) {
                MiniApp.instance().create("mini_app_id", miniAppMessageBridge)
            }
            lifeCycle.addObserver(miniAppDisplay)
    //...
        }
    }
}

To read more about Lifecycle please see link.

On the other hand, when the consuming app manages resources manually or where it has more control on the lifecycle of views MiniAppDisplay.destroyView should be called upon e.g. when removing a view from the view system, yet within the same state of parent’s lifecycle.

API Docs: MiniAppDisplay.navigateBackward, MiniAppDisplay.navigateForward

MiniAppDisplay.navigateBackward and MiniAppDisplay.navigateForward facilitates the navigation inside a mini app if the history stack is available in it. A common usage pattern could be to link it up to the Android Back Key navigation.

override fun onBackPressed() {
    if(!miniAppDisplay.navigateBackward()) {
        super.onBackPressed()
    }
}

External url loader

API Docs: MiniAppNavigator, MiniAppExternalUrlLoader, ExternalResultHandler

The mini app is loaded with the specific custom scheme and custom domain in mini app view.

In default, the external link will be opened in custom tab. See this.

HostApp also can implement their own way by passing MiniAppNavigator object to MiniApp.create(appId: String, miniAppMessageBridge: MiniAppMessageBridge, miniAppNavigator: MiniAppNavigator).

miniAppNavigator = object : MiniAppNavigator {
    override fun openExternalUrl(url: String, externalResultHandler: ExternalResultHandler) {
        // Load external url with own webview.
    }
}

There are two approaches to return mini app url from host app webview to mini app view:

Automatic check in WebView which belongs to separated Activity

If the external webview Activity is different from the Activity running mini app, our SDK provide the auto check and Activity closing by overriding the WebViewClient.

val miniAppExternalUrlLoader = MiniAppExternalUrlLoader(miniAppId, externalWebViewActivity)
class MyWebViewClient(private val miniAppExternalUrlLoader: MiniAppExternalUrlLoader): WebViewClient() {

    override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
        val url = request.url.toString()
        return miniAppExternalUrlLoader.shouldOverrideUrlLoading(url)
    }
}

Return the url result to mini app view:

// externalResultHandler is from MiniAppNavigator implementation.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == externalWebViewReqCode && resultCode == Activity.RESULT_OK) {
            data?.let { intent -> externalResultHandler.emitResult(intent) }
        }
}

Manual check by host app

Host app can take full control and transmit the url back to mini app view.

val miniAppExternalUrlLoader = MiniAppExternalUrlLoader(miniAppId, null)

Using miniAppExternalUrlLoader.shouldClose(url) which returns Boolean to check if it is mini app scheme and should close external webview.

Using #ExternalResultHandler.emitResult(String) to transmit the url string to mini app view.

File choosing

API Docs: MiniAppFileChooser

The mini app is able to choose the file which is requested using HTML forms with ‘file’ input type whenever users press a “Select file” button. HostApp can use a default class provided by the SDK e.g. MiniAppFileChooserDefault to choose the files.

val fileChoosingReqCode = REQUEST_CODE // define a request code in HostApp
val miniAppFileChooser = MiniAppFileChooserDefault(requestCode = fileChoosingReqCode)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    // HostApp can cancel the file choosing operation when resultCode doesn't match
    if (Activity.RESULT_OK != resultCode) {
         miniAppFileChooser.onCancel()
    }

    if (requestCode == fileChoosingReqCode && resultCode == Activity.RESULT_OK) {
        data?.let { intent ->
            miniAppFileChooser.onReceivedFiles(intent)
        }
    }
}

Alternatively, HostApp can use MiniAppFileChooser interface to override onShowFileChooser for customizing file choosing mode and other options.

val miniAppFileChooser = object : MiniAppFileChooser {

        override fun onShowFileChooser(
            filePathCallback: ValueCallback<Array<Uri>>?,
            fileChooserParams: WebChromeClient.FileChooserParams?,
            context: Context
        ): Boolean {
           // write own implementation here.
        }
    }

In both case, HostApp needs to pass MiniAppFileChooser through MiniApp.create(appId: String, miniAppMessageBridge: MiniAppMessageBridge, miniAppFileChooser: MiniAppFileChooser).

Custom Permissions

API Docs: MiniApp.getCustomPermissions, MiniApp.setCustomPermissions, MiniApp.listDownloadedWithCustomPermissions

MiniApp Android SDK supports list of Custom Permissions (MiniAppCustomPermissionType) and these can be stored and retrieved using the following public interfaces.

Retrieving the Mini App Custom Permissions using MiniAppID

Custom permissions and its status can be retrieved using the following interface. getCustomPermissions will return MiniAppCustomPermission that contains the meta-info as a Pair of name and grant result (ALLOWED or DENIED). The custom permissions are stored per each miniAppId.

val permissions = miniapp.getCustomPermissions(miniAppId)

Store the Mini App Custom Permissions

Custom permissions for a mini app are cached by the SDK and you can use the following interface to store permissions when needed.

var permissionPairs = listOf<Pair<MiniAppCustomPermissionType, MiniAppCustomPermissionResult>>()
// .. .. ..
val permissionsToSet = MiniAppCustomPermission(miniAppId, permissionPairs)
miniapp.setCustomPermissions(permissionsToSet)

List of downloaded Mini apps with Custom Permissions

val downloadedMiniApps = miniapp.listDownloadedWithCustomPermissions()
downloadedMiniApps.forEach {
    val miniApp = it.first
    val permissions = it.second
    // Display permissions in view, etc....
}

Passing parameters to a miniapp

For a mini app, you can pass query parameters as String using MiniApp.create to be appended with miniapp’s url. For example: https://mscheme.1234/miniapp/index.html?param1=value1&param2=value2

class MiniAppActivity : Activity(), CoroutineScope {

    override fun onCreate(savedInstanceState: Bundle?) {
    //...
        launch {
            val miniAppDisplay = withContext(Dispatchers.IO) {
                MiniApp.instance().create(
                    appId = "mini_app_id",
                    miniAppMessageBridge = miniAppMessageBridge,
                    miniAppNavigator = miniAppNavigator,
                    queryParams = "param1=value1&param2=value2"
                )
            }
    //...
        }
    }
}

Analytics events

When Analytics SDK is integrated, MiniApp SDK sends analytics data from your app when some events are triggered:

Troubleshooting & FAQs

Exception: “Network requests must not be performed on the main thread.”

Some of the suspending functions in this SDK will perform network requests (MiniApp.create, MiniApp.fetchInfo, MiniApp.listMiniApp). Network requests should not be performed on the main thread, so the above exception will occur if your Coroutine is running in the Dispatchers.Main CoroutineContext. To avoid this exception, please use the Dispatchers.IO or Dispatchers.Default context instead. You can use withContext to make sure your code is running in the appropriate CoroutineContext.

CoroutineScope(Dispatchers.Main).launch {
    withContext(Dispatchers.IO) {
        // Call MiniApp suspending function i.e. `MiniApp.create`
        // This runs in a background thread
    }
        
    // Update your UI - i.e. `setContentView(miniAppView)`
    // This runs on the main thread
}
Exception: MiniAppVerificationException

This exception will be thrown when the SDK cannot verify the security check on local storage using keystore which means that users are not allowed to use miniapp. Some keystores within devices are tampered or OEM were shipped with broken keystore from the beginning.

Build Error: java.lang.RuntimeException: Duplicate class com.rakuten.tech.mobile.manifestconfig.annotations.ManifestConfig

This build error could occur if you are using older versions of other libraries from com.rakuten.tech.mobile. Some of the dependencies in this SDK have changed to a new Group ID of io.github.rakutentech (due to the JCenter shutdown). This means that if you have another library in your project which depends on the older dependencies using the Gropu ID com.rakuten.tech.mobile, then you will have duplicate classes.

To avoid this, please add the following to your build.gradle in order to exclude the old com.rakuten.tech.mobile dependencies from your project.

configurations.all {
    exclude group: 'com.rakuten.tech.mobile', module: 'manifest-config-processor'
    exclude group: 'com.rakuten.tech.mobile', module: 'manifest-config-annotations'
    exclude group: 'com.rakuten.tech.mobile.sdkutils', module: 'sdk-utils'
}

How do I use snapshot versions of this SDK?

We may periodically publish snapshot versions for testing pre-release features. These versions will always end in -SNAPSHOT, for example 1.0.0-SNAPSHOT. If you wish to use a snapshot version, you will need to add the snapshot repo to your Gradle configuration.

repositories {
    maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' }
}

dependency {
    implementation 'io.github.rakutentech.miniapp:miniapp:X.X.X-SNAPSHOT'
}
How do I deep link to mini apps?

If you want to have deep links direclty to your mini apps, then you must implement deep link handling within your App. This can be done using either a custom deep link scheme (such as myAppName://miniapp) or an App Link (such as https://www.example.com/miniapp). See the following Android Developer resources for more information on how to implement deep linking capabilities:

After you have implemented deep linking capabilities in your App, then you can configure your deep link to open and launch a Mini App. Note that your deep link should contain information about which mini app ID to open. Also, you can pass query parameters and a URL fragment to the mini app. The recommended deep link format is similar to https://www.example.com/miniapp/MINI_APP_ID?myParam=myValue#myFragment where the myParam=myValue#myFragment portion is optional and will be passed directly to the mini app.

The following is an example which will parse the mini app ID and query string from a deep link intent:

// In your main Activity
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.main)

    val action: String? = intent?.action
    val uri: Uri? = intent?.data
    
    if (action == Intent.ACTION_VIEW && uri != null) {
        handleDeepLink(uri)
    }
}

fun handleDeepLink(uri: Uri) {
    if (uri.path?.startsWith("/miniapp") == true && uri.lastPathSegment != null) {
        val miniAppId = uri.lastPathSegment
        val queryParams = uri.query ?: ""
        val queryFragment = uri.fragment ?: ""
        val query = "$queryParams#$queryFragment"

        // Note that `MyMiniAppDisplayScreen` is just a placeholder example for your own class
        // Inside this class you should call `MiniApp.create` in order to create and display the mini app
        MyMiniAppDisplayScreen.launch(miniAppId, query)
    }
}
How do I clear the session data for Mini Apps?

In the case that a user logs out of your App, you should clear the session data for all of your Mini Apps. This will ensure that the next user does not have access to the stored sensitive information about the previous user such as Local Storage, IndexedDB, and Web SQL.

The session data can be cleared by using the following:

// Should be called after the User logs out
WebStorage.getInstance().deleteAllData()
CookieManager.getInstance().removeAllCookies {}
WebViewDatabase.getInstance(this).clearHttpAuthUsernamePassword()

Note: This will also clear the storage, cookies, and authentication data for ALL WebViews used by your App.

How can I use this SDK in a Java project?

We don’t support usage of the Mini App SDK in a Java project. This is because this SDK uses Kotlin specific features such as suspend functions and Coroutines.

However, it is possible to use this SDK in a Java project if you wrap the calls to suspend functions into something that Java can use such as a Future or a normal callback interface.

To do this, you will need to integrate Kotlin and Kotlin Coroutines into your project:

// In your top level "build.gradle" file:
buildscript {
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.50"
    }
}

// In your project "myApp/build.gradle" file
apply plugin: 'kotlin-android'

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.50"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.3.0" // Needed if you want to use CoroutineScope.future
}

Then, you will need to create wrapper functions for the Mini App SDK functionality which you wish to use.

If your minimum Android API level is 24 or higher, then you can use Java CompletableFuture:

// MiniAppAsync.kt

private val coroutineScope = CoroutineScope(Dispatchers.Default)

fun createMiniAppAsync(
    appId: String,
    miniAppMessageBridge: MiniAppMessageBridge,
    miniAppNavigator: MiniAppNavigator?
): CompletableFuture<MiniAppDisplay> = coroutineScope.future {
    MiniApp.instance().create(appId, miniAppMessageBridge, miniAppNavigator)
}

If your minimum Android API level is lower than 24, then you can use a normal Java callback interface:

// MiniAppAsync.kt

interface MiniAppCallback<T> {
    fun onSuccess(result: T)
    fun onError(error: MiniAppSdkException)
}

val coroutineScope = CoroutineScope(Dispatchers.Default)

fun createMiniAppAsync(
    appId: String,
    miniAppMessageBridge: MiniAppMessageBridge,
    miniAppNavigator: MiniAppNavigator?,
    callback: MiniAppCallback<MiniAppDisplay>
) {
    coroutineScope.launch {
        try {
            val display = MiniApp.instance().create(appId, miniAppMessageBridge, miniAppNavigator)
            callback.onSuccess(display)
        } catch (error: MiniAppSdkException) {
            callback.onError(error)
        }
    }
}
How to override text for localization purpose?

The MiniApp SDK provides the default UI (i.e custom permission window) when your app does not have own UI implementation.

In case you want to use the default UI and only change text display, you can override the string values in here. Just need to place them in your app strings.xml with the same key. You can also put them in different localization resource directory.

Example: We want to change <string name="miniapp_sdk_android_save">Save</string> in another locale text.

<!--src/main/res/values-ja/strings.xml-->

<string name="miniapp_sdk_android_save">セーブ</string>

Changelog

See the full CHANGELOG.