Skip to content

Commit 66fe3a1

Browse files
WMSDK-445
2 parents 4c6c696 + b44ed40 commit 66fe3a1

File tree

13 files changed

+344
-14
lines changed

13 files changed

+344
-14
lines changed

android/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ android {
3333
targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
3434
versionCode 1
3535
versionName "1.0"
36+
consumerProguardFiles 'consumer-rules.pro'
3637
}
3738

3839
buildTypes {

android/consumer-rules.pro

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-keep class com.facebook.react.ReactHost { *; }
2+
-keep interface com.facebook.react.ReactInstanceEventListener { *; }
3+
-keep interface com.facebook.react.ReactApplication {
4+
public com.facebook.react.ReactHost getReactHost();
5+
}
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
22
package="com.mindboxsdk">
3-
3+
<application>
4+
<provider
5+
android:name="com.mindboxsdk.MindboxSdkInitProvider"
6+
android:authorities="${applicationId}.mindbox.init"
7+
android:exported="false" />
8+
</application>
49
</manifest>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.mindboxsdk
2+
3+
import android.app.Activity
4+
import android.app.Application
5+
import android.content.Context
6+
import android.content.Intent
7+
import android.util.Log
8+
import com.facebook.react.ReactApplication
9+
import com.facebook.react.ReactInstanceManager
10+
import com.facebook.react.bridge.ReactContext
11+
import com.facebook.react.ReactActivity
12+
import cloud.mindbox.mobile_sdk.Mindbox
13+
import cloud.mindbox.mobile_sdk.logger.Level
14+
15+
internal class MindboxEventEmitter (
16+
private val application: Application
17+
) : MindboxEventSubscriber {
18+
19+
private var jsDelivery: MindboxJsDelivery? = null
20+
21+
override fun onEvent(event: MindboxSdkLifecycleEvent) {
22+
when (event) {
23+
is MindboxSdkLifecycleEvent.NewIntent -> handleNewIntent(event.reactContext, event.intent)
24+
is MindboxSdkLifecycleEvent.ActivityCreated -> handleActivityCreated(event.reactContext, event.activity)
25+
is MindboxSdkLifecycleEvent.ActivityDestroyed -> handleActivityDestroyed()
26+
}
27+
}
28+
29+
private fun handleNewIntent(context: ReactContext, intent: Intent) {
30+
Mindbox.writeLog("[RN] Handle new intent in event emitter. ", Level.INFO)
31+
Mindbox.onNewIntent(intent)
32+
Mindbox.onPushClicked(context, intent)
33+
jsDelivery?.sendPushClicked(intent)
34+
}
35+
36+
private fun handleActivityCreated(reactContext:ReactContext, activity: Activity) {
37+
Mindbox.writeLog("[RN] Handle activity created", Level.INFO)
38+
runCatching {
39+
reactContext.let { reactContext ->
40+
initializeAndSendIntent(reactContext, activity)
41+
}
42+
}
43+
}
44+
45+
private fun initializeAndSendIntent(context: ReactContext, activity: Activity) {
46+
Mindbox.writeLog("[RN] Initialize MindboxJsDelivery", Level.INFO)
47+
jsDelivery = MindboxJsDelivery.Shared.getInstance(context)
48+
val currentActivity = context.currentActivity ?: activity
49+
currentActivity.intent?.let { handleNewIntent(context, it) }
50+
}
51+
52+
private fun handleActivityDestroyed() {
53+
jsDelivery = null
54+
}
55+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.mindboxsdk
2+
3+
internal interface MindboxEventSubscriber {
4+
fun onEvent(event: MindboxSdkLifecycleEvent)
5+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.mindboxsdk
2+
3+
import android.app.Application
4+
import android.content.ContentProvider
5+
import android.content.ContentValues
6+
import android.content.pm.PackageManager
7+
import android.database.Cursor
8+
import android.net.Uri
9+
import android.util.Log
10+
11+
internal class MindboxSdkInitProvider : ContentProvider() {
12+
13+
companion object {
14+
private const val AUTO_INIT_ENABLED_KEY = "com.mindbox.sdk.AUTO_INIT_ENABLED"
15+
private const val TAG = "MindboxSdkInitProvider"
16+
}
17+
18+
override fun onCreate(): Boolean {
19+
runCatching {
20+
Log.i(TAG, "onCreate initProvider.")
21+
(context?.applicationContext as? Application)?.let { application ->
22+
if (isAutoInitEnabled(application)) {
23+
Log.i(TAG, "Automatic initialization is enabled.")
24+
MindboxSdkLifecycleListener.register(application)
25+
} else {
26+
Log.i(TAG, "Automatic initialization is disabled.")
27+
}
28+
}
29+
}.onFailure { error ->
30+
Log.e(TAG, "Automatic initialization failed", error)
31+
}
32+
return true
33+
}
34+
35+
private fun isAutoInitEnabled(application: Application): Boolean =
36+
runCatching {
37+
val appInfo = application.packageManager.getApplicationInfo(
38+
application.packageName,
39+
PackageManager.GET_META_DATA
40+
)
41+
appInfo.metaData
42+
?.getBoolean(AUTO_INIT_ENABLED_KEY, false)
43+
?.also { Log.i(TAG, "Result of reading mindbox metadata is $it") }
44+
?: false
45+
}.getOrElse { false }
46+
47+
override fun query(
48+
uri: Uri,
49+
projection: Array<String>?,
50+
selection: String?,
51+
selectionArgs: Array<String>?,
52+
sortOrder: String?
53+
): Cursor? = null
54+
55+
override fun getType(uri: Uri): String? = null
56+
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
57+
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int = 0
58+
override fun update(
59+
uri: Uri,
60+
values: ContentValues?,
61+
selection: String?,
62+
selectionArgs: Array<String>?
63+
): Int = 0
64+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.mindboxsdk
2+
3+
import android.app.Activity
4+
import android.content.Intent
5+
import com.facebook.react.bridge.ReactContext
6+
7+
internal sealed class MindboxSdkLifecycleEvent {
8+
data class NewIntent(val reactContext: ReactContext, val intent: Intent) : MindboxSdkLifecycleEvent()
9+
data class ActivityCreated(val reactContext: ReactContext, val activity: Activity) : MindboxSdkLifecycleEvent()
10+
data class ActivityDestroyed(val activity: Activity) : MindboxSdkLifecycleEvent()
11+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package com.mindboxsdk
2+
3+
import android.app.Activity
4+
import android.app.Application
5+
import android.content.Intent
6+
import android.os.Bundle
7+
import android.util.Log
8+
import com.facebook.react.ReactApplication
9+
import com.facebook.react.ReactInstanceManager
10+
import com.facebook.react.bridge.ActivityEventListener
11+
import com.facebook.react.bridge.ReactContext
12+
import java.util.concurrent.atomic.AtomicBoolean
13+
import cloud.mindbox.mobile_sdk.Mindbox
14+
import cloud.mindbox.mobile_sdk.logger.Level
15+
16+
17+
internal class MindboxSdkLifecycleListener private constructor(
18+
private val application: Application,
19+
private val subscriber: MindboxEventSubscriber
20+
) : Application.ActivityLifecycleCallbacks {
21+
22+
companion object {
23+
fun register(
24+
application: Application,
25+
subscriber: MindboxEventSubscriber = MindboxEventEmitter(application)
26+
) {
27+
val listener = MindboxSdkLifecycleListener(application, subscriber)
28+
application.registerActivityLifecycleCallbacks(listener)
29+
}
30+
}
31+
32+
private val mainActivityClassName: String? by lazy { getLauncherActivityClassName() }
33+
34+
private fun getLauncherActivityClassName(): String? {
35+
val pm = application.packageManager
36+
val intent = pm.getLaunchIntentForPackage(application.packageName)
37+
return intent?.component?.className
38+
}
39+
40+
private fun isMainActivity(activity: Activity): Boolean {
41+
return activity.javaClass.name == mainActivityClassName
42+
}
43+
44+
private var activityEventListener: ActivityEventListener? = null
45+
46+
private fun onReactContextAvailable(reactContext: ReactContext, activity: Activity) {
47+
Mindbox.writeLog("[RN] ReactContext ready", Level.INFO)
48+
addActivityEventListener(reactContext)
49+
subscriber.onEvent(MindboxSdkLifecycleEvent.ActivityCreated(reactContext, activity))
50+
}
51+
52+
private fun registerReactContextListener(
53+
application: Application,
54+
onReady: (ReactContext) -> Unit
55+
) {
56+
val reactApplication = application.getReactApplication() ?: return
57+
val reactInstanceManager = getReactInstanceManager()
58+
59+
val wrapperListener = object : ReactInstanceManager.ReactInstanceEventListener {
60+
private val called = AtomicBoolean(false)
61+
override fun onReactContextInitialized(context: ReactContext) {
62+
if (called.compareAndSet(false, true)) {
63+
onReady(context)
64+
}
65+
}
66+
}
67+
68+
reactInstanceManager?.addReactInstanceEventListener(wrapperListener)
69+
// RN 0.78+ introduced ReactHost.addReactInstanceEventListener(...).
70+
// Older RN versions (<= 0.74) expose only ReactInstanceManager.addReactInstanceEventListener(...).
71+
// In New Architecture the ReactInstanceManager listener might not fire
72+
// To support RN 0.78+ reliably while keeping backward compatibility,
73+
// we try to register via ReactHost using reflection (no compile-time dependency).
74+
// If ReactHost API is unavailable (older RN), this call is silently ignored and we rely on
75+
// the ReactInstanceManager path.
76+
addReactHostListener(application, wrapperListener)
77+
}
78+
79+
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
80+
if (!isMainActivity(activity)) return
81+
82+
getReactInstanceManager()?.currentReactContext?.let {
83+
onReactContextAvailable(it, activity)
84+
Mindbox.writeLog("[RN] ReactContext already available; skipping listener registration ", Level.INFO)
85+
return
86+
}
87+
88+
registerReactContextListener(application) { reactContext ->
89+
onReactContextAvailable(reactContext, activity)
90+
}
91+
}
92+
93+
private fun addActivityEventListener(reactContext: ReactContext) {
94+
activityEventListener?.let { reactContext.removeActivityEventListener(it) }
95+
96+
activityEventListener = object : ActivityEventListener {
97+
override fun onNewIntent(intent: Intent?) {
98+
intent ?: return
99+
reactContext.currentActivity
100+
?.takeIf { isMainActivity(it) }
101+
?.let {
102+
subscriber.onEvent(
103+
MindboxSdkLifecycleEvent.NewIntent(
104+
reactContext,
105+
intent
106+
)
107+
)
108+
}
109+
}
110+
111+
override fun onActivityResult(
112+
activity: Activity?, requestCode: Int, resultCode: Int, data: Intent?
113+
) {
114+
}
115+
}
116+
reactContext.addActivityEventListener(activityEventListener)
117+
}
118+
119+
override fun onActivityDestroyed(activity: Activity) {
120+
if (!isMainActivity(activity)) return
121+
subscriber.onEvent(MindboxSdkLifecycleEvent.ActivityDestroyed(activity))
122+
getReactInstanceManager()
123+
?.currentReactContext
124+
?.removeActivityEventListener(activityEventListener)
125+
activityEventListener = null
126+
}
127+
128+
override fun onActivityStarted(activity: Activity) {}
129+
override fun onActivityResumed(activity: Activity) {}
130+
override fun onActivityPaused(activity: Activity) {}
131+
override fun onActivityStopped(activity: Activity) {}
132+
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
133+
134+
135+
private fun getReactInstanceManager(): ReactInstanceManager? =
136+
application.getReactApplication()?.reactNativeHost?.reactInstanceManager
137+
138+
private fun Application.getReactApplication() = this as? ReactApplication
139+
140+
private fun addReactHostListener(
141+
application: Application,
142+
wrapperListener: ReactInstanceManager.ReactInstanceEventListener
143+
) {
144+
runCatching {
145+
val reactApplication = application as ReactApplication
146+
147+
val hostClass = Class.forName("com.facebook.react.ReactHost")
148+
val listenerClass = Class.forName("com.facebook.react.ReactInstanceEventListener")
149+
150+
val addMethod = hostClass.getMethod("addReactInstanceEventListener", listenerClass)
151+
val getHostMethod = reactApplication.javaClass.getMethod("getReactHost")
152+
val reactHost = getHostMethod.invoke(reactApplication)
153+
154+
val proxy = java.lang.reflect.Proxy.newProxyInstance(
155+
listenerClass.classLoader,
156+
arrayOf(listenerClass)
157+
) { _, method, args ->
158+
if (method.name == "onReactContextInitialized" && args?.size == 1 && args[0] is ReactContext) {
159+
wrapperListener.onReactContextInitialized(args[0] as ReactContext)
160+
}
161+
null
162+
}
163+
164+
addMethod.invoke(reactHost, proxy)
165+
Mindbox.writeLog("[RN] success added react context listener for reactHost", Level.INFO)
166+
}.onFailure {
167+
Mindbox.writeLog("[RN] failed added react context listener for reactHost ", Level.ERROR)
168+
}
169+
}
170+
}

example/exampleApp/android/app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,9 @@
4545
<meta-data
4646
android:name="ru.rustore.sdk.pushclient.project_id"
4747
android:value="l3JnmQZv0CdlZ39y9VERUA8c-P2ys1Ud" />
48+
49+
<meta-data
50+
android:name="com.mindbox.sdk.AUTO_INIT_ENABLED"
51+
android:value="false" />
4852
</application>
4953
</manifest>

example/exampleApp/android/app/src/main/java/com/exampleapp/MainActivity.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import com.facebook.react.defaults.DefaultReactActivityDelegate
1313
import com.mindboxsdk.MindboxJsDelivery
1414

1515
class MainActivity : ReactActivity() {
16-
private var mJsDelivery: MindboxJsDelivery? = null
16+
private var jsDelivery: MindboxJsDelivery? = null
1717
override fun getMainComponentName(): String = "exampleApp"
1818

1919
override fun createReactActivityDelegate(): ReactActivityDelegate =
@@ -22,7 +22,7 @@ class MainActivity : ReactActivity() {
2222
// Initializes MindboxJsDelivery and sends the current intent to React Native
2323
// https://developers.mindbox.ru/docs/flutter-push-navigation-react-native
2424
private fun initializeAndSentIntent(context: ReactContext) {
25-
mJsDelivery = MindboxJsDelivery.Shared.getInstance(context)
25+
jsDelivery = MindboxJsDelivery.Shared.getInstance(context)
2626
if (context.hasCurrentActivity()) {
2727
sendIntent(context, context.getCurrentActivity()!!.getIntent())
2828
} else {
@@ -32,20 +32,20 @@ class MainActivity : ReactActivity() {
3232

3333
override fun onCreate(savedInstanceState: Bundle?) {
3434
super.onCreate(savedInstanceState)
35-
val mReactInstanceManager = getReactNativeHost().getReactInstanceManager();
36-
val reactContext = mReactInstanceManager.getCurrentReactContext();
35+
val reactInstanceManager = getReactNativeHost().getReactInstanceManager();
36+
val reactContext = reactInstanceManager.getCurrentReactContext();
3737

3838
// Initialize and send intent if React context is already available
3939
// https://developers.mindbox.ru/docs/flutter-push-navigation-react-native
4040
if (reactContext != null) {
4141
initializeAndSentIntent(reactContext);
4242
} else {
4343
// Add listener to initialize and send intent once React context is initialized
44-
mReactInstanceManager.addReactInstanceEventListener(object :
44+
reactInstanceManager.addReactInstanceEventListener(object :
4545
ReactInstanceManager.ReactInstanceEventListener {
4646
override fun onReactContextInitialized(context: ReactContext) {
4747
initializeAndSentIntent(context)
48-
mReactInstanceManager.removeReactInstanceEventListener(this)
48+
reactInstanceManager.removeReactInstanceEventListener(this)
4949
}
5050
})
5151
}
@@ -63,6 +63,6 @@ class MainActivity : ReactActivity() {
6363
Mindbox.onNewIntent(intent)
6464
//send click action
6565
Mindbox.onPushClicked(context, intent)
66-
mJsDelivery?.sendPushClicked(intent);
66+
jsDelivery?.sendPushClicked(intent);
6767
}
6868
}

0 commit comments

Comments
 (0)