Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ android {
targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
versionCode 1
versionName "1.0"
consumerProguardFiles 'consumer-rules.pro'
}

buildTypes {
Expand Down
5 changes: 5 additions & 0 deletions android/consumer-rules.pro
Original file line number Diff line number Diff line change
@@ -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();
}
7 changes: 6 additions & 1 deletion android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mindboxsdk">

<application>
<provider
android:name="com.mindboxsdk.MindboxSdkInitProvider"
android:authorities="${applicationId}.mindbox.init"
android:exported="false" />
</application>
</manifest>
55 changes: 55 additions & 0 deletions android/src/main/java/com/mindboxsdk/MindboxEventEmitter.kt
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.mindboxsdk

internal interface MindboxEventSubscriber {
fun onEvent(event: MindboxSdkLifecycleEvent)
}
64 changes: 64 additions & 0 deletions android/src/main/java/com/mindboxsdk/MindboxSdkInitProvider.kt
Original file line number Diff line number Diff line change
@@ -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<String>?,
selection: String?,
selectionArgs: Array<String>?,
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<String>?): Int = 0
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<String>?
): Int = 0
}
11 changes: 11 additions & 0 deletions android/src/main/java/com/mindboxsdk/MindboxSdkLifecycleEvent.kt
Original file line number Diff line number Diff line change
@@ -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()
}
170 changes: 170 additions & 0 deletions android/src/main/java/com/mindboxsdk/MindboxSdkLifecycleListener.kt
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
4 changes: 4 additions & 0 deletions example/exampleApp/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,9 @@
<meta-data
android:name="ru.rustore.sdk.pushclient.project_id"
android:value="l3JnmQZv0CdlZ39y9VERUA8c-P2ys1Ud" />

<meta-data
android:name="com.mindbox.sdk.AUTO_INIT_ENABLED"
android:value="false" />
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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 {
Expand All @@ -32,20 +32,20 @@ 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
if (reactContext != null) {
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)
}
})
}
Expand All @@ -63,6 +63,6 @@ class MainActivity : ReactActivity() {
Mindbox.onNewIntent(intent)
//send click action
Mindbox.onPushClicked(context, intent)
mJsDelivery?.sendPushClicked(intent);
jsDelivery?.sendPushClicked(intent);
}
}
Loading
Loading