layout |
---|
userguide |
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.
{:.no_toc}
- Table of contents {:toc}
- Minimum Android Version: This SDK supports Android 9.0+ (API level 28+).
- Note: Currently this SDK is set to
minSdkVersion 24
, however support for versions 24 to 27 is deprecated and could be removed in a later release.
- Note: Currently this SDK is set to
- Base URL, App ID, Subscription Key: We don't currently provide a public API for use with this SDK. You must provide a URL for your API as well as an App ID and Subscription Key for the API.
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.
Add the following to your build.gradle
file:
repositories {
mavenCentral()
}
dependency {
implementation 'io.github.rakutentech.miniapp:miniapp:${version}'
}
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 |
Require Signature Verification | Boolean | com.rakuten.tech.mobile.miniapp.RequireSignatureVerification |
✅ | 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 |
✅ | 🚫 |
Enable Ad Placement api | Boolean | com.rakuten.tech.mobile.miniapp.EnableH5Ads |
✅ | false |
Note:
- We don't currently host a public API, so you will need to provide your own Base URL for API requests.
- The "Host App User Agent Info" is the string which is appended to user-agent of webview. It should be a meaningful keyword such as host app name to differentiate other host apps.
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}" />
<!-- Using for enabling SDK's settings to verify the signature of the Mini Apps -->
<meta-data
android:name="com.rakuten.tech.mobile.miniapp.RequireSignatureVerification"
android:value="${requireSignatureVerification}" />
<!-- 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>
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:
- Check what is the latest, published version of the mini app.
- Check if the latest version of the mini app has been downloaded.
- If yes, return the already downloaded mini app.
- If no, download the latest version and then return the downloaded version.
- If the device is disconnected from the internet and the device already has a version of the mini app downloaded, then the already downloaded version will be returned.
- If the host app needs the cached version of the miniapp, set
fromCache
value totrue
will return already downloaded version.
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:
- Clean-up:
Clearing up the mini app display is essential.
MiniAppDisplay.destroyView
is required to be called when exit miniapp. - Suspend functions:
This SDK uses
suspend
functions, so you should use Kotlin Coroutines when calling the functions. These examples useDispatchers.IO
, but you can use whicheverCoroutineContext
andCouroutineScope
that is appropriate for your App. However, you MUST NOT useDispatchers.Main
because network requests cannot be performed on the main thread. - Preview Mode:
In preview mode, you can have multiple versions of single miniapp so you can load the specific version with MiniAppInfo object by using
MiniApp.instance().create(MINI_APP_INFO, miniAppMessageBridge)
. - Exceptions:
There are several different types of exceptions which could be thrown by
MiniApp.create
, but all are sub-classes ofMiniAppSdkException
. You can handle each exception type differently if you would like different behavior for different cases. For example you may wish to display a different error message when the server contains no published versions of a mini app. See the full list of exceptions in the API docs.
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 | ✅ |
getHostEnvironmentInfo | ✅ |
The UserInfoBridgeDispatcher
:
Method | Default |
---|---|
getUserName | 🚫 |
getProfilePhoto | 🚫 |
getAccessToken | 🚫 |
getContacts | 🚫 |
getPoints | 🚫 |
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")
}
override fun getHostEnvironmentInfo(
onSuccess: (info: HostEnvironmentInfo) -> Unit,
onError: (infoError: HostEnvironmentInfoError) -> Unit
) {
// Check if there is any environment info in HostApp
if (hasInfo) {
// allow miniapp to invoke the host environment info
onSuccess(hostEnvironmentInfo)
}
else
onError(hostEnvironmentError) // reject miniapp to send host environment info with message explanation.
}
}
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.
}
override fun getPoints(
onSuccess: (points: Points) -> Unit,
onError: (pointsError: MiniAppPointsError) -> Unit
) {
// Check if there is any point in HostApp
// .. .. ..
if (hasPoints)
onSuccess(points) // allow miniapp to get points.
else
onError(pointsError) // reject miniapp to get points 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)
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.
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.
- Location (
MiniAppPermissionType.LOCATION
) (MiniAppPermissionType
has been deprecated) - Location (
MiniAppDevicePermissionType.LOCATION
)
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:
- User name (
MiniAppCustomPermissionType.USER_NAME
) - Profile photo (
MiniAppCustomPermissionType.PROFILE_PHOTO
) - Contact list (
MiniAppCustomPermissionType.CONTACT_LIST
)
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.
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.
API Docs: MiniAppMessageBridge.getHostEnvironmentInfo
The default functionality will provide information using HostEnvironmentInfo
object to Mini App. Also, Host App can send it's environment information by implementing this function.
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.
- User name: string representing the user's name. See UserInfoBridgeDispatcher.getUserName
- Profile photo: URL pointing to a photo. This can also be a Base64 data string. See UserInfoBridgeDispatcher.getProfilePhoto
- Access Token: OAuth 1.0 token including token data and expiration date. Your App will be provided with the ID of the mini app and AccessTokenScope which is requesting the Access Token, so you should verify that this mini app is allowed to use the access token. See UserInfoBridgeDispatcher.getAccessToken
In case the host app wants to use the admob sdk, Add the following to your build.gradle
file:
dependency {
implementation project(':admob-latest')
}
Admob Version
admob-latest
module use Google Play Services Ads version 20.2.0.
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.
API Docs: AdMobDisplayer
Set the AdMobDisplayer
provided by admob-latest
. This controller will handle the display of ad so no work is required from host app.
miniAppMessageBridge.setAdMobDisplayer(AdMobDisplayer(activityContext))
API Docs: miniAppMessageBridge.dispatchNativeEvent
Mini apps are able to get events for custom event types which are defined by the Mini App SDK. These events include things like external webview close, pause, resume.
- External webview close (
NativeEventType.EXTERNAL_WEBVIEW_CLOSE
) - Pause (
NativeEventType.MINIAPP_ON_PAUSE
) - Resume (
NativeEventType.MINIAPP_ON_RESUME
)
Note: Host app can send these events whenever these events occur and MiniApp will be able to get those events.
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())
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.
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)
}
}
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(
appId = "MINI_APP_ID",
versionId = "VERSION_ID",
languageCode = "ja"
)
// 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)
}
}
By passing the languageCode
e.g. en
, ja
in the above getMiniAppManifest
method, you can get the localized description/reason for the permission from the platform API.
If there is no localized description/reason is available, it will return the default value given in the manifest.json
.
In Host App, we can get the downloaded manifest information as following:
val downloadedManifest = MiniApp.instance().getDownloadedManifest("MINI_APP_ID")
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. |
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()
}
}
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)
.
- Implement
MiniAppNavigator
.
miniAppNavigator = object : MiniAppNavigator {
override fun openExternalUrl(url: String, externalResultHandler: ExternalResultHandler) {
// Load external url with own webview.
}
}
-
Create mini app display Using
MiniApp.instance().create("MINI_APP_ID", miniAppMessageBridge, miniAppNavigator)
. In preview mode, usingMiniApp.instance().create(MINI_APP_INFO, miniAppMessageBridge, miniAppNavigator)
. -
Return URL result to mini app view. Some mini apps are loaded their services with external url but in the end that external url will trigger callback or webhook to redirect to mini app custom scheme, mini app custom domain. The external webview / browser cannot recognize mini app url so it is required the return of that url to mini app view.
There are two approaches to return mini app url from host app webview to mini app view:
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) }
}
}
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.
You can also optionally use your MiniAppNavigator
to intercept file download requests from the Mini App. Note that this will only receive files that the Mini App downloaded with XHR, so the URL you receive will be a Base64 data URI. If you do not override the file download functionality, then the SDK will use default functionality to create an Intent for sharing the file to another App.
To use this, you must implement the MiniAppDownloadNavigator
interface in your MiniAppNavigator
implementation.
miniAppNavigator = object : MiniAppNavigator, MiniAppDownloadNavigator {
// Override MiniAppNavigator functions here
// ...
// Override MiniAppDownloadNavigator functions
fun onFileDownloadStart(url: String, userAgent: String, contentDisposition: String, mimetype: String, contentLength: Long) {
// Decode URL as Base 64 and then use it somehow - i.e. save to device, share to another App, etc.
}
}
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.
- At first, HostApp needs to initiate
MiniAppFileChooserDefault
in theActivity
.
val fileChoosingReqCode = REQUEST_CODE // define a request code in HostApp
val miniAppFileChooser = MiniAppFileChooserDefault(requestCode = fileChoosingReqCode)
- Then, HostApp activity can receive the files at
onActivityResult
as following:
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)
.
In case, MiniApp needs to check camera permission or request camera access from HostApp.
val miniAppCameraPermissionDispatcher = object : MiniAppCameraPermissionDispatcher {
override fun getCameraPermission(permissionCallback: (isGranted: Boolean) -> Unit) {
// Check the camera permission of the Device and send it back - i.e. isGranted = true/false.
if (DeviceCameraPermissionGranted)
permissionCallback(true)
else
permissionCallback(false)
}
override fun requestCameraPermission(
miniAppPermissionType: MiniAppDevicePermissionType,
callback: (isGranted: Boolean) -> Unit
) {
// Request the camera permission of the Device send send it back through callback.
callback.invoke(true)
}
}
Dispatch the miniAppCameraPermissionDispatcher
with the MiniAppFileChooserDefault
.
val fileChoosingReqCode = REQUEST_CODE // define a request code in HostApp
val miniAppFileChooser = MiniAppFileChooserDefault(
requestCode = fileChoosingReqCode,
miniAppCameraPermissionDispatcher = miniAppCameraPermissionDispatcher
)
miniAppCameraPermissionDispatcher
is optional, No need to implement this if HostApp doesn't have camera permission in manifest or miniapp doesn't need to access camera.
API Docs: MiniAppFileDownloader
The mini app is able to downlad a file on the local storage. HostApp can use a default class provided by the SDK e.g. MiniAppFileDownloaderDefault
to download the files.
- At first, HostApp needs to initiate
MiniAppFileDownloaderDefault
in theActivity
.
val fileDownloadReqCode = REQUEST_CODE // define a request code in HostApp
val MiniAppFileDownloader = MiniAppFileDownloaderDefault(activity, requestCode = fileDownloadReqCode)
- Then set the
miniappFileDownloader
with theminiAppMessageBridge
.
miniAppMessageBridge.setMiniAppFileDownloader(miniAppFileDownloader)
- Then, HostApp activity can receive the file Uri on
onActivityResult
and pass the Uri to download the file.
if (requestCode == fileDownloadReqCode) {
intent?.data?.let { destinationUri ->
miniAppFileDownloader.onReceivedResult(destinationUri)
}
}
Host app can also implement their own miniAppFileDownloader
by implementing the MiniAppFileDownloader
interface.
val miniAppFileDownloader = object : MiniAppFileDownloader{
override fun onStartFileDownload(
fileName: String,
url: String,
headers: Map<String, String>,
onDownloadSuccess: (String) -> Unit,
onDownloadFailed: (MiniAppDownloadFileError) -> Unit
) {
//.. Download the file
}
}
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.
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)
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)
val downloadedMiniApps = miniapp.listDownloadedWithCustomPermissions()
downloadedMiniApps.forEach {
val miniApp = it.first
val permissions = it.second
// Display permissions in view, etc....
}
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¶m2=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¶m2=value2"
)
}
//...
}
}
}
When Analytics SDK is integrated, MiniApp SDK sends analytics data from your app when some events are triggered:
- HostApp starts with MiniApp SDK.
- When miniapp is launched.
- When miniapp is closed.
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 Group 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'
}
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>
See the full CHANGELOG.