diff --git a/android/build.gradle b/android/build.gradle
index 6930a00..25d6e32 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -33,6 +33,7 @@ android {
targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
versionCode 1
versionName "1.0"
+ consumerProguardFiles 'consumer-rules.pro'
}
buildTypes {
diff --git a/android/consumer-rules.pro b/android/consumer-rules.pro
new file mode 100644
index 0000000..70d3596
--- /dev/null
+++ b/android/consumer-rules.pro
@@ -0,0 +1,5 @@
+-keep class com.facebook.react.ReactHost { *; }
+-keep interface com.facebook.react.ReactInstanceEventListener { *; }
+-keep interface com.facebook.react.ReactApplication {
+ public com.facebook.react.ReactHost getReactHost();
+}
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index dc01239..9c8a289 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -1,4 +1,9 @@
-
+
+
+
diff --git a/android/src/main/java/com/mindboxsdk/MindboxEventEmitter.kt b/android/src/main/java/com/mindboxsdk/MindboxEventEmitter.kt
new file mode 100644
index 0000000..5a6de8e
--- /dev/null
+++ b/android/src/main/java/com/mindboxsdk/MindboxEventEmitter.kt
@@ -0,0 +1,55 @@
+package com.mindboxsdk
+
+import android.app.Activity
+import android.app.Application
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import com.facebook.react.ReactApplication
+import com.facebook.react.ReactInstanceManager
+import com.facebook.react.bridge.ReactContext
+import com.facebook.react.ReactActivity
+import cloud.mindbox.mobile_sdk.Mindbox
+import cloud.mindbox.mobile_sdk.logger.Level
+
+internal class MindboxEventEmitter (
+ private val application: Application
+) : MindboxEventSubscriber {
+
+ private var jsDelivery: MindboxJsDelivery? = null
+
+ override fun onEvent(event: MindboxSdkLifecycleEvent) {
+ when (event) {
+ is MindboxSdkLifecycleEvent.NewIntent -> handleNewIntent(event.reactContext, event.intent)
+ is MindboxSdkLifecycleEvent.ActivityCreated -> handleActivityCreated(event.reactContext, event.activity)
+ is MindboxSdkLifecycleEvent.ActivityDestroyed -> handleActivityDestroyed()
+ }
+ }
+
+ private fun handleNewIntent(context: ReactContext, intent: Intent) {
+ Mindbox.writeLog("[RN] Handle new intent in event emitter. ", Level.INFO)
+ Mindbox.onNewIntent(intent)
+ Mindbox.onPushClicked(context, intent)
+ jsDelivery?.sendPushClicked(intent)
+ }
+
+ private fun handleActivityCreated(reactContext:ReactContext, activity: Activity) {
+ Mindbox.writeLog("[RN] Handle activity created", Level.INFO)
+ runCatching {
+ reactContext.let { reactContext ->
+ initializeAndSendIntent(reactContext, activity)
+ }
+ }
+ }
+
+ private fun initializeAndSendIntent(context: ReactContext, activity: Activity) {
+ Mindbox.writeLog("[RN] Initialize MindboxJsDelivery", Level.INFO)
+ jsDelivery = MindboxJsDelivery.Shared.getInstance(context)
+ val currentActivity = context.currentActivity ?: activity
+ currentActivity.intent?.let { handleNewIntent(context, it) }
+ }
+
+ private fun handleActivityDestroyed() {
+ jsDelivery = null
+ }
+}
diff --git a/android/src/main/java/com/mindboxsdk/MindboxEventSubscriber.kt b/android/src/main/java/com/mindboxsdk/MindboxEventSubscriber.kt
new file mode 100644
index 0000000..ad7105f
--- /dev/null
+++ b/android/src/main/java/com/mindboxsdk/MindboxEventSubscriber.kt
@@ -0,0 +1,5 @@
+package com.mindboxsdk
+
+internal interface MindboxEventSubscriber {
+ fun onEvent(event: MindboxSdkLifecycleEvent)
+}
diff --git a/android/src/main/java/com/mindboxsdk/MindboxSdkInitProvider.kt b/android/src/main/java/com/mindboxsdk/MindboxSdkInitProvider.kt
new file mode 100644
index 0000000..23f8633
--- /dev/null
+++ b/android/src/main/java/com/mindboxsdk/MindboxSdkInitProvider.kt
@@ -0,0 +1,64 @@
+package com.mindboxsdk
+
+import android.app.Application
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.pm.PackageManager
+import android.database.Cursor
+import android.net.Uri
+import android.util.Log
+
+internal class MindboxSdkInitProvider : ContentProvider() {
+
+ companion object {
+ private const val AUTO_INIT_ENABLED_KEY = "com.mindbox.sdk.AUTO_INIT_ENABLED"
+ private const val TAG = "MindboxSdkInitProvider"
+ }
+
+ override fun onCreate(): Boolean {
+ runCatching {
+ Log.i(TAG, "onCreate initProvider.")
+ (context?.applicationContext as? Application)?.let { application ->
+ if (isAutoInitEnabled(application)) {
+ Log.i(TAG, "Automatic initialization is enabled.")
+ MindboxSdkLifecycleListener.register(application)
+ } else {
+ Log.i(TAG, "Automatic initialization is disabled.")
+ }
+ }
+ }.onFailure { error ->
+ Log.e(TAG, "Automatic initialization failed", error)
+ }
+ return true
+ }
+
+ private fun isAutoInitEnabled(application: Application): Boolean =
+ runCatching {
+ val appInfo = application.packageManager.getApplicationInfo(
+ application.packageName,
+ PackageManager.GET_META_DATA
+ )
+ appInfo.metaData
+ ?.getBoolean(AUTO_INIT_ENABLED_KEY, false)
+ ?.also { Log.i(TAG, "Result of reading mindbox metadata is $it") }
+ ?: false
+ }.getOrElse { false }
+
+ override fun query(
+ uri: Uri,
+ projection: Array?,
+ selection: String?,
+ selectionArgs: Array?,
+ sortOrder: String?
+ ): Cursor? = null
+
+ override fun getType(uri: Uri): String? = null
+ override fun insert(uri: Uri, values: ContentValues?): Uri? = null
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int = 0
+ override fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array?
+ ): Int = 0
+}
diff --git a/android/src/main/java/com/mindboxsdk/MindboxSdkLifecycleEvent.kt b/android/src/main/java/com/mindboxsdk/MindboxSdkLifecycleEvent.kt
new file mode 100644
index 0000000..2cce9cf
--- /dev/null
+++ b/android/src/main/java/com/mindboxsdk/MindboxSdkLifecycleEvent.kt
@@ -0,0 +1,11 @@
+package com.mindboxsdk
+
+import android.app.Activity
+import android.content.Intent
+import com.facebook.react.bridge.ReactContext
+
+internal sealed class MindboxSdkLifecycleEvent {
+ data class NewIntent(val reactContext: ReactContext, val intent: Intent) : MindboxSdkLifecycleEvent()
+ data class ActivityCreated(val reactContext: ReactContext, val activity: Activity) : MindboxSdkLifecycleEvent()
+ data class ActivityDestroyed(val activity: Activity) : MindboxSdkLifecycleEvent()
+}
diff --git a/android/src/main/java/com/mindboxsdk/MindboxSdkLifecycleListener.kt b/android/src/main/java/com/mindboxsdk/MindboxSdkLifecycleListener.kt
new file mode 100644
index 0000000..89f2db5
--- /dev/null
+++ b/android/src/main/java/com/mindboxsdk/MindboxSdkLifecycleListener.kt
@@ -0,0 +1,170 @@
+package com.mindboxsdk
+
+import android.app.Activity
+import android.app.Application
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import com.facebook.react.ReactApplication
+import com.facebook.react.ReactInstanceManager
+import com.facebook.react.bridge.ActivityEventListener
+import com.facebook.react.bridge.ReactContext
+import java.util.concurrent.atomic.AtomicBoolean
+import cloud.mindbox.mobile_sdk.Mindbox
+import cloud.mindbox.mobile_sdk.logger.Level
+
+
+internal class MindboxSdkLifecycleListener private constructor(
+ private val application: Application,
+ private val subscriber: MindboxEventSubscriber
+) : Application.ActivityLifecycleCallbacks {
+
+ companion object {
+ fun register(
+ application: Application,
+ subscriber: MindboxEventSubscriber = MindboxEventEmitter(application)
+ ) {
+ val listener = MindboxSdkLifecycleListener(application, subscriber)
+ application.registerActivityLifecycleCallbacks(listener)
+ }
+ }
+
+ private val mainActivityClassName: String? by lazy { getLauncherActivityClassName() }
+
+ private fun getLauncherActivityClassName(): String? {
+ val pm = application.packageManager
+ val intent = pm.getLaunchIntentForPackage(application.packageName)
+ return intent?.component?.className
+ }
+
+ private fun isMainActivity(activity: Activity): Boolean {
+ return activity.javaClass.name == mainActivityClassName
+ }
+
+ private var activityEventListener: ActivityEventListener? = null
+
+ private fun onReactContextAvailable(reactContext: ReactContext, activity: Activity) {
+ Mindbox.writeLog("[RN] ReactContext ready", Level.INFO)
+ addActivityEventListener(reactContext)
+ subscriber.onEvent(MindboxSdkLifecycleEvent.ActivityCreated(reactContext, activity))
+ }
+
+ private fun registerReactContextListener(
+ application: Application,
+ onReady: (ReactContext) -> Unit
+ ) {
+ val reactApplication = application.getReactApplication() ?: return
+ val reactInstanceManager = getReactInstanceManager()
+
+ val wrapperListener = object : ReactInstanceManager.ReactInstanceEventListener {
+ private val called = AtomicBoolean(false)
+ override fun onReactContextInitialized(context: ReactContext) {
+ if (called.compareAndSet(false, true)) {
+ onReady(context)
+ }
+ }
+ }
+
+ reactInstanceManager?.addReactInstanceEventListener(wrapperListener)
+ // RN 0.78+ introduced ReactHost.addReactInstanceEventListener(...).
+ // Older RN versions (<= 0.74) expose only ReactInstanceManager.addReactInstanceEventListener(...).
+ // In New Architecture the ReactInstanceManager listener might not fire
+ // To support RN 0.78+ reliably while keeping backward compatibility,
+ // we try to register via ReactHost using reflection (no compile-time dependency).
+ // If ReactHost API is unavailable (older RN), this call is silently ignored and we rely on
+ // the ReactInstanceManager path.
+ addReactHostListener(application, wrapperListener)
+ }
+
+ override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
+ if (!isMainActivity(activity)) return
+
+ getReactInstanceManager()?.currentReactContext?.let {
+ onReactContextAvailable(it, activity)
+ Mindbox.writeLog("[RN] ReactContext already available; skipping listener registration ", Level.INFO)
+ return
+ }
+
+ registerReactContextListener(application) { reactContext ->
+ onReactContextAvailable(reactContext, activity)
+ }
+ }
+
+ private fun addActivityEventListener(reactContext: ReactContext) {
+ activityEventListener?.let { reactContext.removeActivityEventListener(it) }
+
+ activityEventListener = object : ActivityEventListener {
+ override fun onNewIntent(intent: Intent?) {
+ intent ?: return
+ reactContext.currentActivity
+ ?.takeIf { isMainActivity(it) }
+ ?.let {
+ subscriber.onEvent(
+ MindboxSdkLifecycleEvent.NewIntent(
+ reactContext,
+ intent
+ )
+ )
+ }
+ }
+
+ override fun onActivityResult(
+ activity: Activity?, requestCode: Int, resultCode: Int, data: Intent?
+ ) {
+ }
+ }
+ reactContext.addActivityEventListener(activityEventListener)
+ }
+
+ override fun onActivityDestroyed(activity: Activity) {
+ if (!isMainActivity(activity)) return
+ subscriber.onEvent(MindboxSdkLifecycleEvent.ActivityDestroyed(activity))
+ getReactInstanceManager()
+ ?.currentReactContext
+ ?.removeActivityEventListener(activityEventListener)
+ activityEventListener = null
+ }
+
+ override fun onActivityStarted(activity: Activity) {}
+ override fun onActivityResumed(activity: Activity) {}
+ override fun onActivityPaused(activity: Activity) {}
+ override fun onActivityStopped(activity: Activity) {}
+ override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
+
+
+ private fun getReactInstanceManager(): ReactInstanceManager? =
+ application.getReactApplication()?.reactNativeHost?.reactInstanceManager
+
+ private fun Application.getReactApplication() = this as? ReactApplication
+
+ private fun addReactHostListener(
+ application: Application,
+ wrapperListener: ReactInstanceManager.ReactInstanceEventListener
+ ) {
+ runCatching {
+ val reactApplication = application as ReactApplication
+
+ val hostClass = Class.forName("com.facebook.react.ReactHost")
+ val listenerClass = Class.forName("com.facebook.react.ReactInstanceEventListener")
+
+ val addMethod = hostClass.getMethod("addReactInstanceEventListener", listenerClass)
+ val getHostMethod = reactApplication.javaClass.getMethod("getReactHost")
+ val reactHost = getHostMethod.invoke(reactApplication)
+
+ val proxy = java.lang.reflect.Proxy.newProxyInstance(
+ listenerClass.classLoader,
+ arrayOf(listenerClass)
+ ) { _, method, args ->
+ if (method.name == "onReactContextInitialized" && args?.size == 1 && args[0] is ReactContext) {
+ wrapperListener.onReactContextInitialized(args[0] as ReactContext)
+ }
+ null
+ }
+
+ addMethod.invoke(reactHost, proxy)
+ Mindbox.writeLog("[RN] success added react context listener for reactHost", Level.INFO)
+ }.onFailure {
+ Mindbox.writeLog("[RN] failed added react context listener for reactHost ", Level.ERROR)
+ }
+ }
+}
diff --git a/example/exampleApp/android/app/src/main/AndroidManifest.xml b/example/exampleApp/android/app/src/main/AndroidManifest.xml
index 3152724..5d112d5 100644
--- a/example/exampleApp/android/app/src/main/AndroidManifest.xml
+++ b/example/exampleApp/android/app/src/main/AndroidManifest.xml
@@ -45,5 +45,9 @@
+
+
diff --git a/example/exampleApp/android/app/src/main/java/com/exampleapp/MainActivity.kt b/example/exampleApp/android/app/src/main/java/com/exampleapp/MainActivity.kt
index 5ab883f..790922f 100644
--- a/example/exampleApp/android/app/src/main/java/com/exampleapp/MainActivity.kt
+++ b/example/exampleApp/android/app/src/main/java/com/exampleapp/MainActivity.kt
@@ -13,7 +13,7 @@ import com.facebook.react.defaults.DefaultReactActivityDelegate
import com.mindboxsdk.MindboxJsDelivery
class MainActivity : ReactActivity() {
- private var mJsDelivery: MindboxJsDelivery? = null
+ private var jsDelivery: MindboxJsDelivery? = null
override fun getMainComponentName(): String = "exampleApp"
override fun createReactActivityDelegate(): ReactActivityDelegate =
@@ -22,7 +22,7 @@ class MainActivity : ReactActivity() {
// Initializes MindboxJsDelivery and sends the current intent to React Native
// https://developers.mindbox.ru/docs/flutter-push-navigation-react-native
private fun initializeAndSentIntent(context: ReactContext) {
- mJsDelivery = MindboxJsDelivery.Shared.getInstance(context)
+ jsDelivery = MindboxJsDelivery.Shared.getInstance(context)
if (context.hasCurrentActivity()) {
sendIntent(context, context.getCurrentActivity()!!.getIntent())
} else {
@@ -32,8 +32,8 @@ class MainActivity : ReactActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val mReactInstanceManager = getReactNativeHost().getReactInstanceManager();
- val reactContext = mReactInstanceManager.getCurrentReactContext();
+ val reactInstanceManager = getReactNativeHost().getReactInstanceManager();
+ val reactContext = reactInstanceManager.getCurrentReactContext();
// Initialize and send intent if React context is already available
// https://developers.mindbox.ru/docs/flutter-push-navigation-react-native
@@ -41,11 +41,11 @@ class MainActivity : ReactActivity() {
initializeAndSentIntent(reactContext);
} else {
// Add listener to initialize and send intent once React context is initialized
- mReactInstanceManager.addReactInstanceEventListener(object :
+ reactInstanceManager.addReactInstanceEventListener(object :
ReactInstanceManager.ReactInstanceEventListener {
override fun onReactContextInitialized(context: ReactContext) {
initializeAndSentIntent(context)
- mReactInstanceManager.removeReactInstanceEventListener(this)
+ reactInstanceManager.removeReactInstanceEventListener(this)
}
})
}
@@ -63,6 +63,6 @@ class MainActivity : ReactActivity() {
Mindbox.onNewIntent(intent)
//send click action
Mindbox.onPushClicked(context, intent)
- mJsDelivery?.sendPushClicked(intent);
+ jsDelivery?.sendPushClicked(intent);
}
}
diff --git a/example/exampleApp/android/app/src/main/java/com/exampleapp/MainApplication.kt b/example/exampleApp/android/app/src/main/java/com/exampleapp/MainApplication.kt
index d156ddb..7436ac3 100644
--- a/example/exampleApp/android/app/src/main/java/com/exampleapp/MainApplication.kt
+++ b/example/exampleApp/android/app/src/main/java/com/exampleapp/MainApplication.kt
@@ -21,6 +21,9 @@ import com.facebook.react.modules.core.DeviceEventManagerModule
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import cloud.mindbox.mindbox_rustore.MindboxRuStore
+import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
+import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
+import com.facebook.react.defaults.DefaultReactNativeHost
class MainApplication : Application(), ReactApplication {
@@ -39,6 +42,10 @@ class MainApplication : Application(), ReactApplication {
//The fifth step of https://developers.mindbox.ru/docs/firebase-send-push-notifications-react-native
Mindbox.initPushServices(this, listOf(MindboxFirebase, MindboxHuawei, MindboxRuStore))
SoLoader.init(this, false)
+ if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
+ // If you opted-in for the New Architecture, we load the native entry point for this app.
+ load()
+ }
}
private val gson = Gson()
diff --git a/example/exampleApp/android/build.gradle b/example/exampleApp/android/build.gradle
index 52f2df7..d2f118c 100644
--- a/example/exampleApp/android/build.gradle
+++ b/example/exampleApp/android/build.gradle
@@ -2,10 +2,11 @@ buildscript {
ext {
buildToolsVersion = "34.0.0"
minSdkVersion = 23
- compileSdkVersion = 34
- targetSdkVersion = 34
- ndkVersion = "25.1.8937393"
+ compileSdkVersion = 35
+ targetSdkVersion = 35
+ ndkVersion = "26.1.10909125"
kotlinVersion = "1.8.0"
+ cmakeVersion = "3.22.1"
}
repositories {
google()
diff --git a/example/exampleApp/package.json b/example/exampleApp/package.json
index 8d6e6b4..fcedece 100644
--- a/example/exampleApp/package.json
+++ b/example/exampleApp/package.json
@@ -7,6 +7,8 @@
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
+ "wipe": "rm -rf ./node_modules && rm -f yarn.lock && rm -f yarn-error.log && cd ./ios && rm -rf ./Pods && rm -f Podfile.lock && cd ../android && rm -rf ./build && rm -rf ./.gradle && cd ./app && rm -rf ./build && cd ../../",
+ "assemble-debug": "mkdir -p android/app/src/main/assets && npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/ && cd android && ./gradlew assembleDebug",
"test": "jest"
},
"dependencies": {
@@ -16,10 +18,10 @@
"mindbox-sdk": "^2.13.1",
"react": "18.2.0",
"react-native": "0.74.0",
- "react-native-gesture-handler": "^2.21.2",
- "react-native-permissions": "^5.0.0",
+ "react-native-gesture-handler": "2.21.2",
+ "react-native-permissions": "^5.4.0",
"react-native-safe-area-context": "^4.9.0",
- "react-native-screens": "^3.29.0",
+ "react-native-screens": "^4.0.0",
"react-native-snackbar": "^2.8.0"
},
"devDependencies": {