PageSense Android SDK — React Nativeブリッジ連携ガイド

PageSense Android SDK — React Nativeブリッジ連携ガイド

お知らせ:当社は、お客様により充実したサポート情報を迅速に提供するため、本ページのコンテンツは機械翻訳を用いて日本語に翻訳しています。正確かつ最新のサポート情報をご覧いただくには、本内容の英語版を参照してください。

概要  

このガイドでは、React Native Native Bridgeを使用して、PageSense Android SDKをReact Nativeアプリケーションに組み込む方法を説明します。このブリッジにより、React NativeのJavaScriptコードからAndroid SDKの関数を呼び出し、モバイルアプリケーションでFullStack実験機能を使用できます。
このガイドを完了すると、React NativeアプリケーションのJavaScriptから次のPageSense SDK関数を直接呼び出せるようになります。
  • initialiseSDK
  • activateExperimentByContext
  • getVariationNameByContext
  • trackGoalByContext

React Native Native Bridgeとは  

React Native Native Bridgeは、JavaScriptランタイムとネイティブAndroidコードの間の通信レイヤーです。JavaScriptからネイティブ関数を呼び出したり、プラットフォーム固有のSDK機能にアクセスしたり、ネイティブモジュールから応答を受け取ったりできます。
JavaScriptはKotlinやJavaと直接やり取りできないため、ブリッジが仲介役となります。ネイティブメソッドはKotlinで実装され、React NativeブリッジAPIを通じてReact Nativeに公開されます。


IdeaReact 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開発ツール
  • Android Studio
  • Android SDK
  • Gradle
  • Java Development Kit(JDK)
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にリポジトリーを追加します。
  1. gradle
  2. dependencyResolutionManagement {
  3. repositories {
  4. google()
  5. mavenCentral()
  6. maven {
  7. url 'https://maven.zohodl.com/'
  8. }
  9. }
  10. }
一部のプロジェクト設定では、プロジェクトレベルのbuild.gradleにリポジトリーを追加する場合もあります。
  1. gradle
  2. repositories {
  3. google()
  4. mavenCentral()
  5. maven {
  6. url 'https://maven.zohodl.com/'
  7. }
  8. }
ステップ2 — PageSense Android SDKの依存関係の追加  

android/app/build.gradle:に依存関係を追加します
  1. gradle
  2. dependencies {
  3. implementation('com.facebook.react:react-android')
  4. implementation('com.zoho.pagesense:pagesense:1.1.3')
  5. }

Android向けReact Nativeライブラリー  

次のReact Nativeライブラリーは必須であり、Androidビルド設定の一部として自動的に含まれます。
  • react-android — Android向けReact Nativeのコアランタイムライブラリー。
  • hermes-android(任意)— パフォーマンス向上のためのJavaScriptエンジン。
ネイティブモジュールを実装するには、次のReact Nativeブリッジライブラリーを利用できる必要があります。
  • ReactContextBaseJavaModule
  • ReactMethod
  • Promise
  • ReadableMap
  • ReactPackage
  • ReactApplicationContext

ネイティブブリッジモジュールの作成  

Androidネイティブブリッジは、Kotlinネイティブモジュール、React Nativeパッケージ、パッケージ登録の3つの部分で実装します。

パート1—Kotlinネイティブモジュール  

AndroidアプリケーションのJavaソースパッケージ内にPageSenseSDKModule.ktを作成します。
  1. /ProjectRoot/android/app/src/main/java/com/sampleapp/PageSenseSDKModule.kt
  2. kotlin
  3. packagecom.sampleapp
  4. importcom.facebook.react.bridge.*
  5. importcom.facebook.react.module.annotations.ReactModule
  6. importcom.zoho.pagesense.android.abtesting.PageSenseClient
  7. importcom.zoho.pagesense.android.abtesting.PageSenseClientBuilder
  8. importcom.zoho.pagesense.android.abtesting.PageSenseSDKOptions
  9. importcom.zoho.pagesense.android.abtesting.data.PageSenseUserContext
  10. importcom.zoho.pagesense.android.network.ProjectSettingsCallBack
  11. importcom.zoho.pagesense.android.logging.LogLevel
  12. importcom.zoho.pagesense.android.abtesting.constants.GoalProperty
  13. @ReactModule(name = PageSenseSDKModule.NAME)
  14. class PageSenseSDKModule(private valreactContext: ReactApplicationContext):
  15.     ReactContextBaseJavaModule(reactContext) {
  16.     companion object {
  17.         const valNAME = 'PageSenseSDKModule'
  18.         private const valLOG_TAG = '[PageSenseSDK]'
  19.     }
  20.     private varclient: PageSenseClient? = null
  21.     private varisInitialized = false
  22.     override fun getName(): String {
  23.         returnNAME
  24.     }
  25.     @ReactMethod
  26.     fun initialiseSDK(
  27. accountId: String,
  28. sdkKey: String,
  29. projectName: String,
  30. promise: Promise
  31.     ) {
  32.         if (isInitialized) {
  33.             println('$LOG_TAGSDK already initialized')
  34. promise.resolve(true)
  35.             return
  36.         }
  37.         if (!validateString(accountId, 'accountId')||
  38. !validateString(sdkKey, 'sdkKey')||
  39. !validateString(projectName, 'projectName')) {
  40. promise.resolve(false)
  41.             return
  42.         }
  43.         valpageSenseSDKOptions = PageSenseSDKOptions()
  44. pageSenseSDKOptions.logLevel = LogLevel.DEBUG
  45. pageSenseSDKOptions.pollingInterval = 5
  46. PageSenseClientBuilder.createNewPageSenseClient(
  47. accountId,
  48. sdkKey,
  49. projectName,
  50. pageSenseSDKOptions,
  51.             object: ProjectSettingsCallBack {
  52.                 override fun onFailure(message: String?,code: Int?) {
  53. promise.resolve(false)
  54.                 }
  55.                 override fun onSuccess(data: PageSenseClient?) {
  56.                     if (data!= null) {
  57. client = data
  58. isInitialized = true
  59. promise.resolve(true)
  60.                     } else {
  61. promise.resolve(false)
  62.                     }
  63.                 }
  64.             }
  65.         )
  66.     }
  67.     @ReactMethod
  68.     fun activateExperimentByContext(
  69. experimentName: String,
  70. userContextMap: ReadableMap,
  71. promise: Promise
  72.     ) {
  73.         valclientInstance = client
  74.         if (clientInstance == null) {promise.resolve(null); return }
  75.         if (!validateString(experimentName, 'experimentName')) {promise.resolve(null); return }
  76.         valuserContext = validateAndBuildUserContext(userContextMap)
  77.         if (userContext == null) {promise.resolve(null); return }
  78.         try {
  79. promise.resolve(clientInstance.activateExperiment(experimentName,userContext))
  80.         } catch (e: Exception) {promise.resolve(null) }
  81.     }
  82.     @ReactMethod
  83.     fun getVariationNameByContext(
  84. experimentName: String,
  85. userContextMap: ReadableMap,
  86. promise: Promise
  87.     ) {
  88.         valclientInstance = client
  89.         if (clientInstance == null) {promise.resolve(null); return }
  90.         if (!validateString(experimentName, 'experimentName')) {promise.resolve(null); return }
  91.         valuserContext = validateAndBuildUserContext(userContextMap)
  92.         if (userContext == null) {promise.resolve(null); return }
  93.         try {
  94. promise.resolve(clientInstance.getVariationName(experimentName,userContext))
  95.         } catch (e: Exception) {promise.resolve(null) }
  96.     }
  97.     @ReactMethod
  98.     fun trackGoalByContext(goalName: String,userContextMap: ReadableMap) {
  99.         valclientInstance = client ?: return
  100.         if (!validateString(goalName, 'goalName')) return
  101.         valuserContext = validateAndBuildUserContext(userContextMap)?: return
  102.         try {clientInstance.trackGoal(goalName,userContext) } catch (e: Exception) { }
  103.     }
  104.     @ReactMethod
  105.     fun trackGoalWithPropertiesByContext(
  106. goalName: String,
  107. userContextMap: ReadableMap,
  108. goalPropertiesMap: ReadableMap
  109.     ) {
  110.         valclientInstance = client ?: return
  111.         if (!validateString(goalName, 'goalName')) return
  112.         valuserContext = validateAndBuildUserContext(userContextMap)?: return
  113.         valgoalProperties = validateAndConvertGoalPropertiesNonEmpty(goalPropertiesMap)?: return
  114.         try {clientInstance.trackGoal(goalName,userContext,goalProperties) } catch (e: Exception) { }
  115.     }
  116.     private fun validateString(param: String,paramName: String): Boolean {
  117.         if (param.trim().isEmpty()) {
  118.             println('$LOG_TAG無効な $paramName:空でない文字列である必要があります')
  119.             return false
  120.         }
  121.         return true
  122.     }
  123.     private fun validateAndBuildUserContext(userContextMap: ReadableMap): PageSenseUserContext? {
  124.         if (!userContextMap.hasKey('userId')) return null
  125.         valuserId = userContextMap.getString('userId')
  126.         if (userId.isNullOrBlank()) return null
  127.         valuserAttributes = HashMap<String,String>()
  128.         if (userContextMap.hasKey('userAttributes')&& !userContextMap.isNull('userAttributes')) {
  129.             valrawAttrs = userContextMap.getMap('userAttributes')?: return null
  130.             valiterator = rawAttrs.keySetIterator()
  131.             while (iterator.hasNextKey()) {
  132.                 valkey = iterator.nextKey()
  133.                 if (key.trim().isEmpty()) return null
  134.                 if (rawAttrs.getType(key)!= ReadableType.String) return null
  135. userAttributes[key]= rawAttrs.getString(key)?: return null
  136.             }
  137.         }
  138.         return PageSenseUserContext(userId,userAttributes)
  139.     }
  140.     private fun validateAndConvertGoalPropertiesNonEmpty(
  141. goalPropertiesMap: ReadableMap
  142.     ): HashMap<GoalProperty,String>? {
  143.         valresult = HashMap<GoalProperty,String>()
  144.         valiterator = goalPropertiesMap.keySetIterator()
  145.         if (!iterator.hasNextKey()) return null
  146.         valfreshIterator = goalPropertiesMap.keySetIterator()
  147.         while (freshIterator.hasNextKey()) {
  148.             valkey = freshIterator.nextKey()
  149.             if (key.trim().isEmpty()) return null
  150.             if (goalPropertiesMap.getType(key)!= ReadableType.String) return null
  151.             valvalue = goalPropertiesMap.getString(key)?: return null
  152.             valgoalProperty = when (key.lowercase()) {
  153. GoalProperty.REVENUE.value -> GoalProperty.REVENUE
  154.                 else-> return null
  155.             }
  156. result[goalProperty]= value
  157.         }
  158.         returnresult
  159.     }
  160. }
パート2 — React Nativeパッケージ  

同じディレクトリーにPageSenseSDKPackage.ktを作成します。
  1. /ProjectRoot/android/app/src/main/java/com/sampleapp/PageSenseSDKPackage.kt
  2. kotlin
  3. packagecom.sampleapp
  4. importcom.facebook.react.ReactPackage
  5. importcom.facebook.react.bridge.NativeModule
  6. importcom.facebook.react.bridge.ReactApplicationContext
  7. importcom.facebook.react.uimanager.ViewManager
  8. classPageSenseSDKPackage : ReactPackage {
  9.     override fun createNativeModules(
  10. reactContext: ReactApplicationContext
  11.     ): List<NativeModule> {
  12.         return listOf(PageSenseSDKModule(reactContext))
  13.     }
  14.     override fun createViewManagers(
  15. reactContext: ReactApplicationContext
  16.     ): List<ViewManager<*,*>> {
  17.         return emptyList()
  18.     }
  19. }
  20. パート3—パッケージの登録  
  21. MainApplication.ktでパッケージを登録します。
  22. kotlin
  23. packagecom.sampleapp
  24. importandroid.app.Application
  25. importcom.facebook.react.PackageList
  26. importcom.facebook.react.ReactApplication
  27. importcom.facebook.react.ReactHost
  28. importcom.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
  29. importcom.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
  30. importcom.sampleapp.PageSenseSDKPackage
  31. classMainApplication : Application(),ReactApplication {
  32.     override valreactHost: ReactHost bylazy {
  33.         getDefaultReactHost(
  34. context = applicationContext,
  35. packageList =
  36.                 PackageList(this).packages.apply {
  37.                     add(PageSenseSDKPackage())
  38.                 },
  39.         )
  40.     }
  41.     override fun onCreate() {
  42.         super.onCreate()
  43.         loadReactNative(this)
  44.     }
  45. }
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)を使用してください。




このドキュメントは、各手順をスムーズに進められるように作成されています。さらにサポートが必要な場合やご不明な点がある場合は、support@zohopagesense.com - までお気軽にお問い合わせください。いつでもサポートいたします。