概要
このガイドでは、React Native Native Bridgeを使用して、PageSense Android SDKをReact Nativeアプリケーションに組み込む方法を説明します。このブリッジにより、React NativeのJavaScriptコードからAndroid SDKの関数を呼び出し、モバイルアプリケーションでFullStack実験機能を使用できます。
このガイドを完了すると、React NativeアプリケーションのJavaScriptから次のPageSense SDK関数を直接呼び出せるようになります。
React Native Native Bridgeとは
React Native Native Bridgeは、JavaScriptランタイムとネイティブAndroidコードの間の通信レイヤーです。JavaScriptからネイティブ関数を呼び出したり、プラットフォーム固有のSDK機能にアクセスしたり、ネイティブモジュールから応答を受け取ったりできます。
JavaScriptはKotlinやJavaと直接やり取りできないため、ブリッジが仲介役となります。ネイティブメソッドはKotlinで実装され、React NativeブリッジAPIを通じてReact Nativeに公開されます。
React Native UI
↓
React Nativeラッパー(TypeScript/JavaScript)
↓
React Native Native Bridge
↓
PageSenseSDKPackage(モジュール登録)
↓
PageSenseSDKModule(Kotlinネイティブモジュール)
↓
PageSense Android SDK
React Nativeアプリケーションは主にJavaScriptを実行しますが、PageSense Android SDKはKotlinで実装されています。ネイティブモジュールは、PageSense SDKのメソッドをReact NativeのJavaScriptレイヤーに公開します。React Nativeランタイムに登録されると、このモジュールにはReact NativeのNativeModules APIを通じてアクセスできるようになります。
前提条件
SDKを組み込む前に、次のツールがインストールされていることを確認してください。
開発環境
-
Node.js
-
npm
-
Watchman
-
React Native CLI
Android開発ツール
bash
node-v
npm-v
npx react-native doctor
PageSense Android SDKのインストール
PageSense Android SDKはZoho Maven Repositoryを通じて配布されています。
ステップ1 — Zoho Maven Repositoryの追加
最近のReact Nativeプロジェクト(Gradle 7以降)では、settings.gradleにリポジトリーを追加します。
- gradle
- dependencyResolutionManagement {
- repositories {
- google()
- mavenCentral()
- maven {
- url 'https://maven.zohodl.com/'
- }
- }
- }
一部のプロジェクト設定では、プロジェクトレベルのbuild.gradleにリポジトリーを追加する場合もあります。
- gradle
- repositories {
- google()
- mavenCentral()
- maven {
- url 'https://maven.zohodl.com/'
- }
- }
ステップ2 — PageSense Android SDKの依存関係の追加
android/app/build.gradle:に依存関係を追加します
- gradle
- dependencies {
- implementation('com.facebook.react:react-android')
- implementation('com.zoho.pagesense:pagesense:1.1.3')
- }
Android向けReact Nativeライブラリー
次のReact Nativeライブラリーは必須であり、Androidビルド設定の一部として自動的に含まれます。
ネイティブモジュールを実装するには、次のReact Nativeブリッジライブラリーを利用できる必要があります。
ネイティブブリッジモジュールの作成
Androidネイティブブリッジは、Kotlinネイティブモジュール、React Nativeパッケージ、パッケージ登録の3つの部分で実装します。
パート1—Kotlinネイティブモジュール
AndroidアプリケーションのJavaソースパッケージ内にPageSenseSDKModule.ktを作成します。
- /ProjectRoot/android/app/src/main/java/com/sampleapp/PageSenseSDKModule.kt
- kotlin
- packagecom.sampleapp
- importcom.facebook.react.bridge.*
- importcom.facebook.react.module.annotations.ReactModule
- importcom.zoho.pagesense.android.abtesting.PageSenseClient
- importcom.zoho.pagesense.android.abtesting.PageSenseClientBuilder
- importcom.zoho.pagesense.android.abtesting.PageSenseSDKOptions
- importcom.zoho.pagesense.android.abtesting.data.PageSenseUserContext
- importcom.zoho.pagesense.android.network.ProjectSettingsCallBack
- importcom.zoho.pagesense.android.logging.LogLevel
- importcom.zoho.pagesense.android.abtesting.constants.GoalProperty
- @ReactModule(name = PageSenseSDKModule.NAME)
- class PageSenseSDKModule(private valreactContext: ReactApplicationContext):
- ReactContextBaseJavaModule(reactContext) {
- companion object {
- const valNAME = 'PageSenseSDKModule'
- private const valLOG_TAG = '[PageSenseSDK]'
- }
- private varclient: PageSenseClient? = null
- private varisInitialized = false
- override fun getName(): String {
- returnNAME
- }
- @ReactMethod
- fun initialiseSDK(
- accountId: String,
- sdkKey: String,
- projectName: String,
- promise: Promise
- ) {
- if (isInitialized) {
- println('$LOG_TAGSDK already initialized')
- promise.resolve(true)
- return
- }
- if (!validateString(accountId, 'accountId')||
- !validateString(sdkKey, 'sdkKey')||
- !validateString(projectName, 'projectName')) {
- promise.resolve(false)
- return
- }
- valpageSenseSDKOptions = PageSenseSDKOptions()
- pageSenseSDKOptions.logLevel = LogLevel.DEBUG
- pageSenseSDKOptions.pollingInterval = 5
- PageSenseClientBuilder.createNewPageSenseClient(
- accountId,
- sdkKey,
- projectName,
- pageSenseSDKOptions,
- object: ProjectSettingsCallBack {
- override fun onFailure(message: String?,code: Int?) {
- promise.resolve(false)
- }
- override fun onSuccess(data: PageSenseClient?) {
- if (data!= null) {
- client = data
- isInitialized = true
- promise.resolve(true)
- } else {
- promise.resolve(false)
- }
- }
- }
- )
- }
- @ReactMethod
- fun activateExperimentByContext(
- experimentName: String,
- userContextMap: ReadableMap,
- promise: Promise
- ) {
- valclientInstance = client
- if (clientInstance == null) {promise.resolve(null); return }
- if (!validateString(experimentName, 'experimentName')) {promise.resolve(null); return }
- valuserContext = validateAndBuildUserContext(userContextMap)
- if (userContext == null) {promise.resolve(null); return }
- try {
- promise.resolve(clientInstance.activateExperiment(experimentName,userContext))
- } catch (e: Exception) {promise.resolve(null) }
- }
- @ReactMethod
- fun getVariationNameByContext(
- experimentName: String,
- userContextMap: ReadableMap,
- promise: Promise
- ) {
- valclientInstance = client
- if (clientInstance == null) {promise.resolve(null); return }
- if (!validateString(experimentName, 'experimentName')) {promise.resolve(null); return }
- valuserContext = validateAndBuildUserContext(userContextMap)
- if (userContext == null) {promise.resolve(null); return }
- try {
- promise.resolve(clientInstance.getVariationName(experimentName,userContext))
- } catch (e: Exception) {promise.resolve(null) }
- }
- @ReactMethod
- fun trackGoalByContext(goalName: String,userContextMap: ReadableMap) {
- valclientInstance = client ?: return
- if (!validateString(goalName, 'goalName')) return
- valuserContext = validateAndBuildUserContext(userContextMap)?: return
- try {clientInstance.trackGoal(goalName,userContext) } catch (e: Exception) { }
- }
- @ReactMethod
- fun trackGoalWithPropertiesByContext(
- goalName: String,
- userContextMap: ReadableMap,
- goalPropertiesMap: ReadableMap
- ) {
- valclientInstance = client ?: return
- if (!validateString(goalName, 'goalName')) return
- valuserContext = validateAndBuildUserContext(userContextMap)?: return
- valgoalProperties = validateAndConvertGoalPropertiesNonEmpty(goalPropertiesMap)?: return
- try {clientInstance.trackGoal(goalName,userContext,goalProperties) } catch (e: Exception) { }
- }
- private fun validateString(param: String,paramName: String): Boolean {
- if (param.trim().isEmpty()) {
- println('$LOG_TAG無効な $paramName:空でない文字列である必要があります')
- return false
- }
- return true
- }
- private fun validateAndBuildUserContext(userContextMap: ReadableMap): PageSenseUserContext? {
- if (!userContextMap.hasKey('userId')) return null
- valuserId = userContextMap.getString('userId')
- if (userId.isNullOrBlank()) return null
- valuserAttributes = HashMap<String,String>()
- if (userContextMap.hasKey('userAttributes')&& !userContextMap.isNull('userAttributes')) {
- valrawAttrs = userContextMap.getMap('userAttributes')?: return null
- valiterator = rawAttrs.keySetIterator()
- while (iterator.hasNextKey()) {
- valkey = iterator.nextKey()
- if (key.trim().isEmpty()) return null
- if (rawAttrs.getType(key)!= ReadableType.String) return null
- userAttributes[key]= rawAttrs.getString(key)?: return null
- }
- }
- return PageSenseUserContext(userId,userAttributes)
- }
- private fun validateAndConvertGoalPropertiesNonEmpty(
- goalPropertiesMap: ReadableMap
- ): HashMap<GoalProperty,String>? {
- valresult = HashMap<GoalProperty,String>()
- valiterator = goalPropertiesMap.keySetIterator()
- if (!iterator.hasNextKey()) return null
- valfreshIterator = goalPropertiesMap.keySetIterator()
- while (freshIterator.hasNextKey()) {
- valkey = freshIterator.nextKey()
- if (key.trim().isEmpty()) return null
- if (goalPropertiesMap.getType(key)!= ReadableType.String) return null
- valvalue = goalPropertiesMap.getString(key)?: return null
- valgoalProperty = when (key.lowercase()) {
- GoalProperty.REVENUE.value -> GoalProperty.REVENUE
- else-> return null
- }
- result[goalProperty]= value
- }
- returnresult
- }
- }
パート2 — React Nativeパッケージ
同じディレクトリーにPageSenseSDKPackage.ktを作成します。
- /ProjectRoot/android/app/src/main/java/com/sampleapp/PageSenseSDKPackage.kt
- kotlin
- packagecom.sampleapp
- importcom.facebook.react.ReactPackage
- importcom.facebook.react.bridge.NativeModule
- importcom.facebook.react.bridge.ReactApplicationContext
- importcom.facebook.react.uimanager.ViewManager
- classPageSenseSDKPackage : ReactPackage {
- override fun createNativeModules(
- reactContext: ReactApplicationContext
- ): List<NativeModule> {
- return listOf(PageSenseSDKModule(reactContext))
- }
- override fun createViewManagers(
- reactContext: ReactApplicationContext
- ): List<ViewManager<*,*>> {
- return emptyList()
- }
- }
- パート3—パッケージの登録
- MainApplication.ktでパッケージを登録します。
- kotlin
- packagecom.sampleapp
- importandroid.app.Application
- importcom.facebook.react.PackageList
- importcom.facebook.react.ReactApplication
- importcom.facebook.react.ReactHost
- importcom.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
- importcom.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
- importcom.sampleapp.PageSenseSDKPackage
- classMainApplication : Application(),ReactApplication {
- override valreactHost: ReactHost bylazy {
- getDefaultReactHost(
- context = applicationContext,
- packageList =
- PackageList(this).packages.apply {
- add(PageSenseSDKPackage())
- },
- )
- }
- override fun onCreate() {
- super.onCreate()
- loadReactNative(this)
- }
- }
GoalPropertyのマッピング
Androidでは、PageSense SDKはゴールプロパティをHashMap<GoalProperty, String>として受け取ります。ここで、GoalPropertyはcom.zoho.pagesense.android.abtesting.constantsで定義されている定数クラスです。React Nativeブリッジは、JavaScriptから文字列キー/valueのペアのReadableMapとしてゴールプロパティを受け取り、各文字列キーを対応するGoalProperty定数にマッピングします。
|
JavaScriptキー
|
文字列値
|
AndroidのGoalProperty定数
|
|
GoalProperty.REVENUE
|
'revenue'
|
GoalProperty.REVENUE
|
ベストプラクティス
-
プロジェクトでは、アプリケーションのライフサイクルごとにSDKを1回だけ初期化してください。
-
実験をすぐに利用できるように、アプリケーションの起動時にSDKを初期化してください。
-
SDKの初期化に失敗しても、UIのレンダリングをブロックしないでください。必ず代替UIを使用してください。
-
アプリケーションセッション全体でPageSenseClientインスタンスを維持してください。
-
実験を適切に追跡できるように、アプリケーションセッション全体で一貫したユーザーIDを使用してください。
-
ユーザーセッションごとにPageSenseUserContextを1回作成し、SDK呼び出し全体で再利用してください。
-
収益ゴールを追跡する場合は、収益値を必ずセント単位の文字列として渡してください。ドル金額に100を掛け、最も近い整数に丸めてください。
-
ハードコードされた文字列ではなく、JavaScriptラッパーからエクスポートされたGoalProperty定数を使用してください。
-
バリエーションがnullの場合は、常に安全な既定のUIにフォールバックしてください。
-
特定のユーザー操作またはイベントが正常に完了した後に、ゴールをトリガーしてください。
-
新しい連携ではすべて、コンテキストベースのSDK関数(activateExperimentByContext、getVariationNameByContext、trackGoalByContext)を使用してください。