Skip to content
WhaleAppSDK

Android

Complete WhaleAppSDK integration and public API reference for Android

Requirements

  • Min SDK 24 and Target SDK 35.
  • JDK 17 or later, Kotlin 1.8.22 or later, AGP 8.5.2 or later, and Gradle 8.7 or later.
  • Java and Kotlin Broker Apps are supported.
  • arm64-v8a and armeabi-v7a architectures are supported.
  • Android Studio 2025.1.3 or later is recommended.
  • Landscape is not supported.

Install the SDK

Extract LBWhaleAppSDK-XXX.zip, which contains the AARs and Demo, and place the SDK directory in the project. In the App-level Gradle file, load the delivered configuration:

// Adjust the path to the actual SDK location.
apply from: "${project.file('LBWhaleAppSDK/whale-sdk-config.gradle')}"

ARouter preprocessing is required

WhaleAppSDK uses ARouter internally. Without the preprocessing plugin, init becomes substantially slower.

// Project-level build.gradle
buildscript {
    dependencies {
        classpath files('./../LBWhaleAppSDK/libs/lb-arouter-register-1.1.4.jar')
    }
}
// App-level build.gradle
plugins {
    id("com.longbridge.arouter")
}

Kapt and Data Binding

apply plugin: 'kotlin-kapt'
plugins {
    id("org.jetbrains.kotlin.kapt")
}

android {
    buildFeatures {
        viewBinding = true
        dataBinding = true
    }
}

R8 compatibility mode

Add this to the root gradle.properties:

android.enableR8.fullMode=false

Initialize SDK

Call init on the main thread from Application.onCreate:

LBWhaleApp.init(application, debug = false)

Without ARouter preprocessing enabled, this call becomes substantially slower.

Configure push

See Message-push integration for server-channel selection, cloud setup, and the standard message shape.

Whale-managed channel

Initialize the push service on the main thread after LBWhaleApp.init:

val notificationConfig = NotificationConfig(
    notificationIcon = R.mipmap.logo_notification,
    notificationSmallIcon = R.mipmap.lb_push_small_notification,
    notificationChannelId = "trading-messages"
)

val pushConfig = LBWhalePushConfig.Builder()
    .appKey("<aliyun-push-app-key>")
    .appSecret("<aliyun-push-app-secret>")
    .disableMultiDevice(false)
    .notificationConfig(notificationConfig)
    .pushCallback(object : LBWhalePushCallback {
        override fun onNotificationOpened(
            title: String,
            summary: String,
            extMap: Map<String, String>
        ) {
            if (LBWhaleApp.isStarted()) {
                LBWhaleApp.pushService.handleNotificationOpened(
                    title, summary, extMap
                )
            } else {
                // Start the SDK, then handle the pending click.
            }
        }

        override fun onPushRegisterFailed(
            errorCode: String?,
            errorMessage: String?
        ) {
            reportPushRegistrationFailure(errorCode, errorMessage)
        }
    })
    .build()

LBWhaleApp.pushService.init(application, pushConfig)

Vendor-channel push (FCM)

WhaleAppSDK’s built-in Aliyun push supports blending in third-party vendor channels to improve delivery. The following example forwards SDK messages through FCM.

Report the FCM token and pass payload-bearing messages to the SDK:

override fun onNewToken(token: String) {
    LBWhaleApp.pushService.repotThirdToken(
        this, LBWhalePushService.ThirdPush.FCM, token
    )
}

override fun onMessageReceived(message: RemoteMessage) {
    message.data["payload"]?.takeIf { it.isNotEmpty() }?.let { payload ->
        LBWhaleApp.pushService.onThirdPushMsg(
            this, LBWhalePushService.ThirdPush.FCM, payload
        )
    }
}

repotThirdToken is the actual public API spelling in the current SDK.

Broker-managed channel

Broker Server delivers Whale’s standard message through Broker’s own provider. Broker App may pass either parsed fields or the complete standard JSON to the SDK:

val result = LBWhaleApp.pushService.handleNotificationReceived(messageJson)

val clickResult = LBWhaleApp.pushService.handleNotificationOpened(messageJson)

The field overloads accept title, summary, and the complete user_info map. Do not flatten or rename fields in user_info.

Both methods return LBWhaleAppResult<Unit>:

when (val result = LBWhaleApp.pushService.handleNotificationOpened(messageJson)) {
    is LBWhaleAppResult.Success -> Unit
    is LBWhaleAppResult.Error -> reportSanitized(result.cause)
}

Possible causes include NotInitialized, UnrecognizedPushMessage, MissingPushLink, and InternalError.

Notification event handling

Notification display

In-app notifications:

  • Whale-managed channel: handled internally by the SDK.
  • Broker-managed channel: call LBWhaleApp.pushService.handleNotificationReceived after receiving a message to parse and display it. This only takes effect after the SDK has started.

Out-of-app (system tray) notifications:

  • Whale-managed channel: handled internally by the SDK.
  • Broker-managed channel: handled by Broker App.

Notification click events

In-app notification tapped:

  • Whale-managed channel: handled internally by the SDK.
  • Broker-managed channel: if LBWhaleApp.pushService.handleNotificationReceived was used to display the message, the SDK also handles the tap; otherwise Broker App must handle it.

Out-of-app (system tray) notification tapped:

  • Whale-managed channel: delivered through LBWhalePushCallback. Broker App may handle it directly, or call LBWhaleApp.pushService.handleNotificationOpened, which parses the notification and navigates to the matching page. This only takes effect after the SDK has started.
  • Broker-managed channel: call LBWhaleApp.pushService.handleNotificationOpened to parse the notification and navigate to the matching page. This only takes effect after the SDK has started.

Start SDK

Start the SDK before opening any SDK page:

val config = LBWhaleAppConfig(
    appName = LBWhaleAppConfigName(
        en = "My Trading App",
        zhCN = "我的交易应用",
        zhHK = "我的交易應用"
    ),
    appKey = "<app-key>",
    appSecret = "<app-secret>",
    appId = "<app-id>",
    token = "<client-token>",
    refreshToken = "<client-refresh-token>",
    theme = LBWhaleAppConfigTheme.AUTO,
    language = LBWhaleAppConfigLanguage.EN,
    priceColor = LBWhaleAppConfigPriceColor.FOLLOW_USER_SETTING,
    defaultAccountChannel = "<account-channel>",
    webDomainPrefix = "<web-domain-prefix>",
    env = LBWhaleAppConfigEnv.PROD
)

LBWhaleApp.startWithConfig(application, config)

Startup is asynchronous. The result is delivered through the callback registered in Implement the callback.

Warning

Use only the environment and credentials delivered for the project. Do not hard-code real appSecret, token, or refresh-token values in the repository.

Implement the callback

Register LBWhaleApp.callback before calling startWithConfig:

LBWhaleApp.callback = object : LBWhaleAppCallback {
    override fun onLBWhaleAppStarted() {
        // SDK-dependent UI may now be opened.
    }

    override fun onLBWhaleAppStartFailed(error: Exception) {
        reportSanitized(error)
        when (error) {
            is LBWhaleAppError.InvalidToken,
            is LBWhaleAppError.TokenExpired,
            is LBWhaleAppError.RefreshTokenExpired -> redirectToLogin()
            is LBWhaleAppError.InvalidAppKey,
            is LBWhaleAppError.InvalidAppSecret,
            is LBWhaleAppError.InvalidConfig -> showIntegrationError(error)
            is LBWhaleAppError.SyncAccountFailed,
            is LBWhaleAppError.RequestFailed -> showRetryableError(error)
            else -> showRetryableError(error)
        }
    }

    override fun onLBWhaleAppRunInError(error: Exception) {
        if (error is LBWhaleAppError.RefreshTokenExpired) {
            LBWhaleApp.logoutAndDestroy()
            redirectToLogin()
        }
    }

    override fun onLBWhaleAppTokenChanged(token: String, refreshToken: String) {
        // Notification only. The SDK continues to manage validity and renewal.
    }

    override fun onWhaleAppSdkOpenUrlRequested(url: String) {
        brokerRouter.openValidated(url)
    }

    override fun onLBWhaleAppRefreshTokenExpired(
        resolver: LBWhaleAppRefreshTokenResolver
    ) {
        resolver.resolve("", "")
    }
}

WhaleAppSDK validates and renews credentials internally. Broker App supplies the initial token and refreshToken; it does not call check-token or refresh-token APIs.

onLBWhaleAppRefreshTokenExpired is an exceptional fallback after normal renewal fails. The default implementation above abandons retry, after which the runtime error callback reports RefreshTokenExpired so Broker App can destroy the SDK and return to the logged-out state. Only resolve with a completely new credential pair when the project has explicitly agreed on a separate way to obtain one.

Note

Under normal operation, WhaleAppSDK completes token validation and renewal automatically, and Broker App never calls a check-token or refresh-token API directly. onLBWhaleAppRefreshTokenExpired is a fallback extension point for a rare failure case, not a channel to implement a regular refresh flow.

Implement the UI callback (optional)

LBWhaleAppUICallback reports when the Client enters or exits SDK pages. It is independent of callback; register it only if needed.

  • Enter SDK: fires when the first SDK page opens (live SDK page count 0 → 1).
  • Exit SDK: fires when the last SDK page closes (live SDK page count 1 → 0).
  • Determined by whether a page exists (onCreate / onDestroy), not by foreground/background visibility — moving Broker App to the background while an SDK page is still alive does not count as an exit.
  • Destroying the SDK (logoutAndDestroy) emits one more exit callback if an SDK page is still alive at that point.
  • Both callbacks run on the main thread and may update UI directly. Each method has a default empty implementation, so override only the ones needed.
LBWhaleApp.uiCallback = object : LBWhaleAppUICallback {
    override fun onLBWhaleAppEnterSdkPage() {
        // First live SDK page opened (0 → 1).
    }

    override fun onLBWhaleAppExitSdkPage() {
        // Last live SDK page closed (1 → 0).
    }
}

Basic usage

Warning

Call these methods only after the SDK has started successfully.

Page routing

Use the router to open SDK pages:

LBWhaleApp.pushUrl("lb://page/main")

val bundle = Bundle().apply { putString("id", "ST/US/AAPL") }
LBWhaleApp.pushUrl(
    "lb://page/stock/detail",
    bundle,
    Intent.FLAG_ACTIVITY_NEW_TASK
)

Validate any URL received by onWhaleAppSdkOpenUrlRequested before opening it in Broker App.

Change theme

LBWhaleApp.changeTheme(LBWhaleAppConfigTheme.AUTO)

Related: switch the price-up/price-down color scheme with changePriceColor:

LBWhaleApp.changePriceColor(LBWhaleAppConfigPriceColor.FOLLOW_USER_SETTING)

Change language

LBWhaleApp.changeLanguage(LBWhaleAppConfigLanguage.EN)

Log out and destroy the SDK

LBWhaleApp.logoutAndDestroy()

Call this when the Client signs out, switches accounts, or the SDK reports unrecoverable authentication failure. It closes SDK pages, stops SDK activity, and clears the current startup configuration, releasing memory and network requests.

Error types

LBWhaleAppError When it occurs Handling
InvalidConfig(field) A required startup field is empty Correct integration configuration
NotInitialized A state-dependent API is called before startup succeeds Wait for onLBWhaleAppStarted
SyncAccountFailed Account synchronization fails during startup Check network and retry startup
InvalidAppKey / InvalidAppSecret Project credentials are rejected Verify the delivered configuration
InvalidToken / TokenExpired Initial Client credential is invalid Obtain a new sign-in session
RefreshTokenExpired The session cannot be renewed Destroy the SDK and return to logged-out state
RequestFailed(message, code) Another network request fails Log sanitized code and message and apply project retry policy
UnrecognizedPushMessage Message is not a Whale message Let Broker App handle it
MissingPushLink Recognized message has no route Do not navigate; report sanitized metadata
InternalError(cause) Unexpected routing or SDK operation failure Report the sanitized stack through project monitoring

Dependency conflicts

Customized third-party libraries

The delivered SDK customizes, patches, or upgrades several open-source third-party libraries. If Broker App depends on the original version of one of these, a code conflict can occur.

The following libraries may conflict:

Package Upstream project
com.github.zhpanvip:bannerviewpager BannerViewPager
com.liulishuo.filedownloader:library FileDownloader
com.github.barteksc:android-pdf-viewer AndroidPdfViewerV2, PdfiumAndroid
com.zhihu.android:matisse Matisse
com.contrarywind:Android-PickerView Android-PickerView
skin.support:skin-support Android-skin-support
com.github.tbruyelle:rxpermissions RxPermissions
com.github.gzu-liyujiang Android_CN_OAID

If the project depends on the original library directly, remove that dependency and use the SDK’s customized version instead. If another third-party library pulls it in transitively, exclude it in Gradle:

implementation("xxxxxxxxxx") {
    exclude group: "xxxxx", module: "xxxxxx"
}

Public API reference

LBWhaleApp

var callback: LBWhaleAppCallback?
var uiCallback: LBWhaleAppUICallback?
val pushService: LBWhalePushService

fun init(application: Application, debug: Boolean = false)
fun startWithConfig(application: Application, config: LBWhaleAppConfig)
fun logoutAndDestroy()
fun pushUrl(uriString: String, bundle: Bundle? = null, flags: Int? = null)
fun changeTheme(theme: LBWhaleAppConfigTheme)
fun changeLanguage(language: LBWhaleAppConfigLanguage)
fun changePriceColor(priceColor: LBWhaleAppConfigPriceColor)
fun isStarted(): Boolean
fun getStatus(): Status
fun getVersion(): String
fun getAppId(): String

Status values are IDLE, STARTING, STARTED, and RELEASING.

LBWhalePushService

Available through LBWhaleApp.pushService.

fun init(application: Application, pushConfig: LBWhalePushConfig)
fun repotThirdToken(context: Context, thirdPush: ThirdPush, token: String)
fun onThirdPushMsg(context: Context, thirdPush: ThirdPush, msg: String?)
fun getPushDeviceId(): String

fun handleNotificationReceived(
    title: String,
    summary: String,
    extraMap: Map<String, String>,
    callback: NotificationRouterCallback? = null
): LBWhaleAppResult<Unit>

fun handleNotificationReceived(
    json: String,
    callback: NotificationRouterCallback? = null
): LBWhaleAppResult<Unit>

fun handleNotificationOpened(
    title: String,
    summary: String,
    extMap: Map<String, String>,
    callback: NotificationRouterCallback? = null
): LBWhaleAppResult<Unit>

fun handleNotificationOpened(
    json: String,
    callback: NotificationRouterCallback? = null
): LBWhaleAppResult<Unit>

fun interface NotificationRouterCallback {
    fun onRouter(url: String)
}

enum class ThirdPush { FCM }

The complete-JSON overloads read title, body, and user_info, then call the field overloads. Both field overloads are generated for Java through @JvmOverloads.

handleNotificationReceived and handleNotificationOpened return LBWhaleAppResult<Unit>: LBWhaleAppResult.Success on success, or LBWhaleAppResult.Error on failure — read the specific LBWhaleAppError through cause. Broker-managed channels should prefer passing the standard message shape rather than reconstructing user_info by hand.

sealed class LBWhaleAppResult<out T> {
    data class Success<T>(val value: T) : LBWhaleAppResult<T>()
    data class Error(val cause: Throwable) : LBWhaleAppResult<Nothing>()

    val isSuccess: Boolean
    val isError: Boolean

    companion object {
        @JvmField val Ok: LBWhaleAppResult<Unit>
    }
}

Kotlin extensions include onSuccess, onError, map, fold, getOrNull, and getOrElse.

Startup configuration

class LBWhaleAppConfig(
    val appName: LBWhaleAppConfigName,
    val appKey: String,
    val appSecret: String,
    val appId: String,
    val token: String,
    val refreshToken: String,
    var theme: LBWhaleAppConfigTheme = LBWhaleAppConfigTheme.AUTO,
    var language: LBWhaleAppConfigLanguage = LBWhaleAppConfigLanguage.EN,
    var priceColor: LBWhaleAppConfigPriceColor =
        LBWhaleAppConfigPriceColor.FOLLOW_USER_SETTING,
    val defaultAccountChannel: String = "",
    val webDomainPrefix: String = "",
    val extraCustomConfig: Map<String, Any>? = null,
    val env: LBWhaleAppConfigEnv = LBWhaleAppConfigEnv.PROD
)

See Application name, Theme mode, Display language, Runtime environment, and Price and color display below for the related types.

Application name

data class LBWhaleAppConfigName(
    val en: String,
    val zhCN: String? = null,
    val zhHK: String? = null
)

en is required; zhCN and zhHK are optional.

Theme mode

enum class LBWhaleAppConfigTheme {
    AUTO,
    LIGHT,
    DARK
}

Display language

enum class LBWhaleAppConfigLanguage {
    EN,
    ZH_CN,
    ZH_HK
}

Runtime environment

enum class LBWhaleAppConfigEnv {
    PROD,
    SIT,
    TEST
}

Do not select a non-production environment unless the Whale project team supplies matching configuration.

Price and color display

enum class LBWhaleAppConfigPriceColor(val value: Int) {
    FOLLOW_USER_SETTING(0),
    RED_UP_GREEN_DOWN(1),
    GREEN_UP_RED_DOWN(2)
}

RED_UP_GREEN_DOWN and GREEN_UP_RED_DOWN hide the in-SDK price-color setting entry point. FOLLOW_USER_SETTING keeps that entry point visible and follows the user’s own in-SDK choice.

Advanced configuration

extraCustomConfig supports project-level font and push-tip color overrides, the debug panel flag, and the exceptional renewal callback timeout. Use the constants in ExtraCustomConfigKey; do not copy string literals into Broker App.

Constant Value Purpose
LB_APPSDK_CONFIG_DEBUG_EGG Boolean Enable the debug panel
LB_APPSDK_CONFIG_KEY_FONT_CRYPTO Typeface Market-data digit font
LB_APPSDK_CONFIG_KEY_FONT_MONOSPACED Typeface Regular monospaced font
LB_APPSDK_CONFIG_KEY_FONT_MONOSPACED_BOLD Typeface Bold monospaced font
LB_APPSDK_CONFIG_KEY_COLOR Map Root color map
LB_APPSDK_CONFIG_KEY_PUSH_TIP_VIEW Map Push-tip color map
LB_APPSDK_CONFIG_KEY_DEVICE_ID String Broker-provided device identifier
LB_APPSDK_CONFIG_KEY_REFRESH_TOKEN_RESOLVER_TIMEOUT seconds Exceptional renewal callback timeout; defaults to 30 seconds

Push-tip color maps support order-completed, order-failed, order-limit, other-message, default background, title, content, and icon values. Light and dark values use LB_APPSDK_CONFIG_KEY_COLOR_LIGHT and LB_APPSDK_CONFIG_KEY_COLOR_DARK.

Push configuration

Built through LBWhalePushConfig.Builder().

class LBWhalePushConfig private constructor(
    val appKey: String,
    val appSecret: String,
    val disableMultiDevice: Boolean,
    val notificationConfig: NotificationConfig,
    val pushCallback: LBWhalePushCallback?
) {
    class Builder {
        fun appKey(appKey: String): Builder
        fun appSecret(appSecret: String): Builder
        fun disableMultiDevice(disableMultiDevice: Boolean): Builder
        fun notificationConfig(config: NotificationConfig): Builder
        fun pushCallback(callback: LBWhalePushCallback): Builder
        fun build(): LBWhalePushConfig
    }
}

Notification configuration

Create directly through the constructor. All three fields are required, or an IllegalArgumentException is thrown.

data class NotificationConfig(
    val notificationIcon: Int,
    val notificationSmallIcon: Int,
    val notificationChannelId: String = ""
)

FCM push configuration

All four fields of this public data type are required, or an IllegalArgumentException is thrown. The current LBWhalePushConfig.Builder does not accept this object — configure FCM through Broker App and forward its token and payload through repotThirdToken and onThirdPushMsg; do not infer a builder API from older README examples.

data class FCMPushConfig(
    val sendId: String,
    val applicationId: String,
    val projectId: String,
    val apiKey: String
)

SDK event callback

interface LBWhaleAppCallback {
    fun onLBWhaleAppStarted()
    fun onLBWhaleAppStartFailed(error: Exception)
    fun onLBWhaleAppRunInError(error: Exception)
    fun onLBWhaleAppTokenChanged(token: String, refreshToken: String)
    fun onWhaleAppSdkOpenUrlRequested(url: String)
    fun onLBWhaleAppAnalyticsEvent(eventName: String, properties: JSONObject) {}
    fun onLBWhaleAppRefreshTokenExpired(
        resolver: LBWhaleAppRefreshTokenResolver
    ) {
        resolver.resolve("", "")
    }
}

onLBWhaleAppAnalyticsEvent has a default empty implementation; override it only when the project needs to capture SDK analytics events:

override fun onLBWhaleAppAnalyticsEvent(
    eventName: String,
    properties: JSONObject
) {
    analytics.track(eventName, properties)
}

Forward only event names and properties agreed for the project. Never append credentials or Client-sensitive information.

Push event callback

interface LBWhalePushCallback {
    fun onNotificationOpened(
        title: String,
        summary: String,
        extMap: Map<String, String>
    )

    fun onPushRegisterFailed(errorCode: String?, errorMessage: String?) {}
}

SDK page lifecycle callback

interface LBWhaleAppUICallback {
    fun onLBWhaleAppEnterSdkPage() {}
    fun onLBWhaleAppExitSdkPage() {}
}
Whale Docs