react-native-mapp-plugin 1.4.1 → 2.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,20 @@
1
1
  apply plugin: "com.android.library"
2
2
 
3
+ import groovy.json.JsonSlurper
4
+
3
5
  def getExtOrDefault = { name, fallback ->
4
6
  rootProject.ext.has(name) ? rootProject.ext.get(name) : fallback
5
7
  }
6
8
 
9
+ def reactNativePackage = [
10
+ file("../node_modules/react-native/package.json"),
11
+ file("../../react-native/package.json")
12
+ ].find { it.exists() }
13
+ if (reactNativePackage == null) {
14
+ throw new GradleException("react-native-mapp-plugin could not resolve react-native/package.json")
15
+ }
16
+ def resolvedReactNativeVersion = new JsonSlurper().parse(reactNativePackage).version
17
+
7
18
  android {
8
19
  namespace "com.reactlibrary"
9
20
  compileSdk = getExtOrDefault("compileSdkVersion", 36)
@@ -29,34 +40,153 @@ android {
29
40
  }
30
41
 
31
42
  dependencies {
32
- //noinspection GradleDynamicVersion
33
- implementation "com.facebook.react:react-native:+"
43
+ implementation "com.facebook.react:react-android:${resolvedReactNativeVersion}"
44
+ api platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.11.0")
45
+ api enforcedPlatform("org.jetbrains.kotlin:kotlin-bom:2.1.20")
34
46
  constraints {
35
- implementation("androidx.core:core") {
47
+ api("androidx.core:core") {
36
48
  version {
37
49
  strictly "1.18.0"
38
50
  }
39
51
  because "androidx.core 1.19.0 requires AGP 9.1+, while React Native dependencies currently require the app to stay on AGP 8"
40
52
  }
41
- implementation("androidx.core:core-ktx") {
53
+ api("androidx.core:core-ktx") {
42
54
  version {
43
55
  strictly "1.18.0"
44
56
  }
45
- because "androidx.core-ktx 1.19.0 requires AGP 9.1+, while React Native dependencies currently require the app to stay on AGP 8"
57
+ because "androidx.core-ktx 1.19.0 requires AGP 9.1+, while React Native dependencies currently require the app to stay on AGP 8"
58
+ }
59
+ ["work-runtime", "work-runtime-ktx"].each { module ->
60
+ api("androidx.work:${module}") {
61
+ version { strictly "2.10.5" }
62
+ because "Expo SDK 57 uses AGP 8.12 and cannot consume WorkManager 2.11 metadata"
63
+ }
64
+ }
65
+ [
66
+ "lifecycle-common",
67
+ "lifecycle-common-java8",
68
+ "lifecycle-livedata-core",
69
+ "lifecycle-livedata-core-ktx",
70
+ "lifecycle-process",
71
+ "lifecycle-runtime",
72
+ "lifecycle-runtime-android",
73
+ "lifecycle-runtime-ktx",
74
+ "lifecycle-service",
75
+ "lifecycle-viewmodel",
76
+ "lifecycle-viewmodel-android",
77
+ "lifecycle-viewmodel-ktx",
78
+ "lifecycle-viewmodel-savedstate"
79
+ ].each { module ->
80
+ api("androidx.lifecycle:${module}") {
81
+ version { strictly "2.10.0" }
82
+ because "Expo SDK 57 uses AGP 8.12 and cannot consume Lifecycle 2.11 metadata"
83
+ }
84
+ }
85
+ api("com.google.android.gms:play-services-location") {
86
+ version { strictly "21.3.0" }
87
+ because "21.4.0 requires an Android toolchain newer than the Expo SDK 57 baseline"
88
+ }
89
+ ["kotlinx-coroutines-android", "kotlinx-coroutines-core", "kotlinx-coroutines-core-jvm"].each { module ->
90
+ api("org.jetbrains.kotlinx:${module}") {
91
+ version { strictly "1.11.0" }
92
+ because "Mapp Engage 7.1.2 native in-app UI requires the coroutines 1.11 Job ABI"
93
+ }
46
94
  }
47
95
  }
48
96
  implementation 'com.google.code.gson:gson:2.14.0'
49
97
  implementation 'androidx.appcompat:appcompat:1.7.1'
50
98
  implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7'
51
- implementation platform('com.google.firebase:firebase-bom:33.16.0')
52
99
  implementation('com.google.firebase:firebase-messaging')
53
100
  api('com.google.android.gms:play-services-location:21.3.0')
54
101
  implementation 'androidx.media:media:1.8.0'
55
102
  implementation 'androidx.legacy:legacy-support-v13:1.0.0'
56
- implementation("com.mapp.sdk:engage-android:7.1.1")
103
+ implementation("com.mapp.sdk:engage-android:7.1.2") {
104
+ exclude group: "androidx.lifecycle", module: "lifecycle-extensions"
105
+ }
57
106
  testImplementation 'junit:junit:4.13.2'
58
107
  testImplementation 'org.mockito:mockito-core:5.23.0'
59
108
  testImplementation 'org.mockito:mockito-inline:5.2.0'
60
109
  testImplementation 'org.json:json:20260522'
61
110
  testImplementation 'org.robolectric:robolectric:4.16.1'
62
111
  }
112
+
113
+ tasks.register("verifyExpo57RuntimeDependencies") {
114
+ group = "verification"
115
+ description = "Checks the Expo SDK 57 runtime pins and Mapp's required coroutines ABI."
116
+ doLast {
117
+ def expected = [
118
+ "androidx.core:core": "1.18.0",
119
+ "androidx.core:core-ktx": "1.18.0",
120
+ "androidx.work:work-runtime": "2.10.5",
121
+ "androidx.lifecycle:lifecycle-runtime": "2.10.0",
122
+ "com.google.android.gms:play-services-location": "21.3.0",
123
+ "org.jetbrains.kotlin:kotlin-stdlib": "2.1.20",
124
+ "org.jetbrains.kotlinx:kotlinx-coroutines-android": "1.11.0",
125
+ "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm": "1.11.0"
126
+ ]
127
+ def artifacts = configurations.debugRuntimeClasspath.resolvedConfiguration.resolvedArtifacts
128
+ def selected = configurations.debugRuntimeClasspath.incoming.resolutionResult.allComponents
129
+ .findAll { it.moduleVersion != null }
130
+ .collectEntries { component ->
131
+ [("${component.moduleVersion.group}:${component.moduleVersion.name}".toString()): component.moduleVersion.version]
132
+ }
133
+ expected.each { module, version ->
134
+ if (selected[module] != version) {
135
+ throw new GradleException("Expected ${module}:${version}, resolved ${selected[module] ?: 'nothing'}")
136
+ }
137
+ }
138
+ selected.findAll { module, ignored -> module.startsWith("androidx.lifecycle:") }.each { module, version ->
139
+ if (module != "androidx.lifecycle:lifecycle-extensions" && version != "2.10.0") {
140
+ throw new GradleException("Expected Lifecycle 2.10.0, resolved ${module}:${version}")
141
+ }
142
+ }
143
+ if (selected.containsKey("androidx.lifecycle:lifecycle-extensions")) {
144
+ throw new GradleException("lifecycle-extensions must not be present in the runtime graph")
145
+ }
146
+
147
+ def coroutineJar = artifacts.find {
148
+ it.moduleVersion.id.group == "org.jetbrains.kotlinx"
149
+ && it.moduleVersion.id.name == "kotlinx-coroutines-core-jvm"
150
+ }?.file
151
+ if (coroutineJar == null) {
152
+ throw new GradleException("Could not inspect kotlinx-coroutines-core-jvm")
153
+ }
154
+ def javap = new ProcessBuilder(
155
+ "${System.getProperty('java.home')}/bin/javap",
156
+ "-classpath",
157
+ coroutineJar.absolutePath,
158
+ "kotlinx.coroutines.Job"
159
+ ).redirectErrorStream(true).start()
160
+ def javapOutput = javap.inputStream.getText("UTF-8")
161
+ if (javap.waitFor() != 0) {
162
+ throw new GradleException("Unable to inspect the resolved coroutines Job ABI:\n${javapOutput}")
163
+ }
164
+ if (!javapOutput.contains('cancel$default(kotlinx.coroutines.Job, java.util.concurrent.CancellationException, int, java.lang.Object)')) {
165
+ throw new GradleException("Resolved coroutines Job is missing Mapp's required cancel\$default ABI")
166
+ }
167
+
168
+ def apiElements = configurations.getByName("releaseApiElements")
169
+ def exportedConstraints = apiElements.allDependencyConstraints.collect {
170
+ "${it.group}:${it.name}:${it.versionConstraint.strictVersion}".toString()
171
+ }
172
+ [
173
+ "androidx.work:work-runtime:2.10.5",
174
+ "androidx.lifecycle:lifecycle-runtime:2.10.0",
175
+ "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.11.0"
176
+ ].each { constraint ->
177
+ if (!exportedConstraints.contains(constraint)) {
178
+ throw new GradleException("Compatibility constraint is not exported: ${constraint}")
179
+ }
180
+ }
181
+ def exportedDependencies = apiElements.allDependencies.collect {
182
+ "${it.group}:${it.name}:${it.version}".toString()
183
+ }
184
+ if (!exportedDependencies.contains("org.jetbrains.kotlin:kotlin-bom:2.1.20")) {
185
+ throw new GradleException("The Expo Kotlin compatibility platform is not exported")
186
+ }
187
+ }
188
+ }
189
+
190
+ tasks.named("check").configure {
191
+ dependsOn("verifyExpo57RuntimeDependencies")
192
+ }
@@ -38,5 +38,3 @@ newArchEnabled=true
38
38
  # Use this property to enable or disable the Hermes JS engine.
39
39
  # If set to false, you will be using JSC instead.
40
40
  hermesEnabled=false
41
-
42
- org.gradle.java.home=/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home
@@ -1,13 +1,14 @@
1
1
  pluginManagement {
2
+ includeBuild("../node_modules/@react-native/gradle-plugin")
2
3
  repositories {
3
4
  google()
4
5
  mavenCentral()
5
6
  gradlePluginPortal()
6
7
  }
7
- plugins {
8
- id("com.android.library") version("9.1.1") // 👈 for library
9
- id("com.facebook.react") version("0.84.1") // 👈 for React Native
10
- }
8
+ }
9
+
10
+ plugins {
11
+ id("com.android.library") version("8.12.0") apply false
11
12
  }
12
13
 
13
14
  dependencyResolutionManagement {
@@ -17,6 +18,4 @@ dependencyResolutionManagement {
17
18
  mavenCentral()
18
19
  }
19
20
  }
20
- includeBuild("../node_modules/@react-native/gradle-plugin")
21
-
22
21
  rootProject.name="MappEngagePlugin"
@@ -42,6 +42,14 @@
42
42
 
43
43
  </intent-filter>
44
44
  </activity>
45
+ <service
46
+ android:name="com.appoxee.shared.MappMessagingService"
47
+ tools:node="remove"
48
+ android:exported="false">
49
+ <intent-filter>
50
+ <action android:name="com.google.firebase.MESSAGING_EVENT" />
51
+ </intent-filter>
52
+ </service>
45
53
  <service
46
54
  android:name="com.appoxee.push.fcm.MappMessagingService"
47
55
  tools:node="remove"
@@ -71,4 +79,3 @@
71
79
  </application>
72
80
 
73
81
  </manifest>
74
-
@@ -0,0 +1,102 @@
1
+ package com.reactlibrary;
2
+
3
+ import android.app.Application;
4
+ import android.os.Handler;
5
+ import android.os.Looper;
6
+ import android.util.Log;
7
+
8
+ import androidx.annotation.NonNull;
9
+ import androidx.annotation.Nullable;
10
+
11
+ import com.appoxee.Appoxee;
12
+ import com.appoxee.shared.AppoxeeOptions;
13
+
14
+ import java.util.concurrent.CountDownLatch;
15
+ import java.util.concurrent.TimeUnit;
16
+ import java.util.concurrent.atomic.AtomicBoolean;
17
+
18
+ /** Keeps every Appoxee.engage call on Android's main looper. */
19
+ final class MappEngagementDispatcher {
20
+ private static final String TAG = "MappEngage";
21
+ static final long ENGAGE_TIMEOUT_MILLIS = 5_000;
22
+
23
+ private MappEngagementDispatcher() {}
24
+
25
+ static void engageAsync(
26
+ @NonNull Application application,
27
+ @Nullable AppoxeeOptions options,
28
+ @Nullable Runnable afterEngage
29
+ ) {
30
+ Runnable operation = () -> {
31
+ if (engageNow(application, options) && afterEngage != null) {
32
+ afterEngage.run();
33
+ }
34
+ };
35
+ if (Looper.myLooper() == Looper.getMainLooper()) {
36
+ operation.run();
37
+ return;
38
+ }
39
+ if (!new Handler(Looper.getMainLooper()).post(operation)) {
40
+ Log.e(TAG, "Unable to post Mapp initialization to the main looper");
41
+ }
42
+ }
43
+
44
+ static boolean engageBlocking(@NonNull Application application, @Nullable AppoxeeOptions options) {
45
+ return engageBlocking(
46
+ application,
47
+ options,
48
+ new Handler(Looper.getMainLooper()),
49
+ ENGAGE_TIMEOUT_MILLIS
50
+ );
51
+ }
52
+
53
+ static boolean engageBlocking(
54
+ @NonNull Application application,
55
+ @Nullable AppoxeeOptions options,
56
+ @NonNull Handler mainHandler,
57
+ long timeoutMillis
58
+ ) {
59
+ if (Looper.myLooper() == Looper.getMainLooper()) {
60
+ return engageNow(application, options);
61
+ }
62
+
63
+ CountDownLatch completion = new CountDownLatch(1);
64
+ AtomicBoolean engaged = new AtomicBoolean(false);
65
+ boolean posted = mainHandler.post(() -> {
66
+ try {
67
+ engaged.set(engageNow(application, options));
68
+ } finally {
69
+ completion.countDown();
70
+ }
71
+ });
72
+ if (!posted) {
73
+ Log.e(TAG, "Unable to post Mapp initialization to the main looper");
74
+ return false;
75
+ }
76
+
77
+ try {
78
+ if (!completion.await(timeoutMillis, TimeUnit.MILLISECONDS)) {
79
+ Log.e(TAG, "Mapp initialization timed out on the main looper");
80
+ return false;
81
+ }
82
+ return engaged.get();
83
+ } catch (InterruptedException interrupted) {
84
+ Thread.currentThread().interrupt();
85
+ Log.w(TAG, "Interrupted while waiting for Mapp initialization", interrupted);
86
+ return false;
87
+ }
88
+ }
89
+
90
+ private static boolean engageNow(
91
+ @NonNull Application application,
92
+ @Nullable AppoxeeOptions options
93
+ ) {
94
+ try {
95
+ Appoxee.engage(application, options);
96
+ return true;
97
+ } catch (RuntimeException error) {
98
+ Log.e(TAG, "Mapp initialization failed", error);
99
+ return false;
100
+ }
101
+ }
102
+ }
@@ -0,0 +1,126 @@
1
+ package com.reactlibrary;
2
+
3
+ import android.app.Application;
4
+ import android.os.Handler;
5
+ import android.util.Log;
6
+
7
+ import androidx.annotation.NonNull;
8
+
9
+ import com.appoxee.Appoxee;
10
+ import com.google.firebase.messaging.RemoteMessage;
11
+
12
+ import java.util.concurrent.TimeUnit;
13
+
14
+ /**
15
+ * Native entry points for applications that own their FirebaseMessagingService.
16
+ * This API does not require a running React Native JavaScript runtime.
17
+ */
18
+ public final class MappPushHelper {
19
+ private static final String TAG = "MappPushHelper";
20
+ private static final int READY_ATTEMPTS = 15;
21
+ private static final long READY_INTERVAL_MILLIS = 300;
22
+
23
+ private MappPushHelper() {}
24
+
25
+ /** Engage Mapp using AppoxeeConfig/native defaults if it has not been engaged yet. */
26
+ public static boolean initialize(@NonNull Application application) {
27
+ return MappEngagementDispatcher.engageBlocking(application, null);
28
+ }
29
+
30
+ static boolean initialize(@NonNull Application application, @NonNull Handler handler, long timeoutMillis) {
31
+ return MappEngagementDispatcher.engageBlocking(application, null, handler, timeoutMillis);
32
+ }
33
+
34
+ /** Return true only when the native Mapp SDK identifies this payload as its own. */
35
+ public static boolean isMappMessage(@NonNull RemoteMessage remoteMessage) {
36
+ try {
37
+ return Appoxee.instance().isPushMessageFromMapp(remoteMessage);
38
+ } catch (RuntimeException error) {
39
+ Log.e(TAG, "Unable to inspect remote message", error);
40
+ return false;
41
+ }
42
+ }
43
+
44
+ /** Forward a Mapp message. Returns false for non-Mapp payloads or SDK failures. */
45
+ public static boolean handleMessage(@NonNull Application application, @NonNull RemoteMessage remoteMessage) {
46
+ return handleMessage(application, remoteMessage, null, MappEngagementDispatcher.ENGAGE_TIMEOUT_MILLIS);
47
+ }
48
+
49
+ static boolean handleMessage(
50
+ @NonNull Application application,
51
+ @NonNull RemoteMessage remoteMessage,
52
+ Handler handler,
53
+ long timeoutMillis
54
+ ) {
55
+ boolean initialized = handler == null
56
+ ? initialize(application)
57
+ : initialize(application, handler, timeoutMillis);
58
+ if (!initialized) {
59
+ return false;
60
+ }
61
+ if (!isMappMessage(remoteMessage) || !waitUntilReady()) {
62
+ return false;
63
+ }
64
+ try {
65
+ Appoxee.instance().handlePushMessage(remoteMessage);
66
+ return true;
67
+ } catch (RuntimeException error) {
68
+ Log.e(TAG, "Unable to forward remote message", error);
69
+ return false;
70
+ }
71
+ }
72
+
73
+ /** Forward a refreshed native FCM token without requiring JavaScript. */
74
+ public static boolean handleNewToken(@NonNull Application application, @NonNull String token) {
75
+ return handleNewToken(application, token, null, MappEngagementDispatcher.ENGAGE_TIMEOUT_MILLIS);
76
+ }
77
+
78
+ static boolean handleNewToken(
79
+ @NonNull Application application,
80
+ @NonNull String token,
81
+ Handler handler,
82
+ long timeoutMillis
83
+ ) {
84
+ if (token.trim().isEmpty()) {
85
+ return false;
86
+ }
87
+ boolean initialized = handler == null
88
+ ? initialize(application)
89
+ : initialize(application, handler, timeoutMillis);
90
+ if (!initialized) {
91
+ return false;
92
+ }
93
+ if (!waitUntilReady()) {
94
+ return false;
95
+ }
96
+ try {
97
+ Appoxee.instance().updateFirebaseToken(token).enqueue(result -> {});
98
+ return true;
99
+ } catch (RuntimeException error) {
100
+ Log.e(TAG, "Unable to forward Firebase token", error);
101
+ return false;
102
+ }
103
+ }
104
+
105
+ public static boolean waitUntilReady() {
106
+ for (int attempt = 0; attempt <= READY_ATTEMPTS; attempt++) {
107
+ try {
108
+ if (Appoxee.instance().isReady()) {
109
+ return true;
110
+ }
111
+ if (attempt < READY_ATTEMPTS) {
112
+ TimeUnit.MILLISECONDS.sleep(READY_INTERVAL_MILLIS);
113
+ }
114
+ } catch (InterruptedException interrupted) {
115
+ Thread.currentThread().interrupt();
116
+ Log.w(TAG, "Interrupted while waiting for Mapp initialization", interrupted);
117
+ return false;
118
+ } catch (RuntimeException error) {
119
+ Log.e(TAG, "Mapp initialization failed", error);
120
+ return false;
121
+ }
122
+ }
123
+ Log.w(TAG, "Mapp was not ready before the bounded wait expired");
124
+ return false;
125
+ }
126
+ }
@@ -4,13 +4,9 @@ import android.util.Log;
4
4
 
5
5
  import androidx.annotation.NonNull;
6
6
 
7
- import com.appoxee.Appoxee;
8
- import com.appoxee.shared.AppoxeeOptions;
9
7
  import com.google.firebase.messaging.FirebaseMessagingService;
10
8
  import com.google.firebase.messaging.RemoteMessage;
11
9
 
12
- import java.util.concurrent.TimeUnit;
13
-
14
10
 
15
11
  /**
16
12
  * Updated for Engage SDK v7:
@@ -23,36 +19,20 @@ public class MessageService extends FirebaseMessagingService {
23
19
  @Override
24
20
  public void onCreate() {
25
21
  super.onCreate();
26
- Appoxee.engage(getApplication(), null);
22
+ MappPushHelper.initialize(getApplication());
27
23
  }
28
24
 
29
25
  @Override
30
26
  public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
31
27
  Log.d("onMessageReceived", remoteMessage.toString());
32
- if (Appoxee.instance().isPushMessageFromMapp(remoteMessage)) {
33
- waitInitialization();
34
- Appoxee.instance().handlePushMessage(remoteMessage);
35
- } else {
28
+ if (!MappPushHelper.handleMessage(getApplication(), remoteMessage)) {
36
29
  super.onMessageReceived(remoteMessage);
37
30
  }
38
31
  }
39
32
 
40
33
  @Override
41
34
  public void onNewToken(@NonNull String token) {
42
- waitInitialization();
43
- Appoxee.instance().updateFirebaseToken(token).enqueue(result -> {});
35
+ MappPushHelper.handleNewToken(getApplication(), token);
44
36
  super.onNewToken(token);
45
37
  }
46
-
47
- private void waitInitialization() {
48
- int limit = 15;
49
- try {
50
- while (limit >= 0 && !Appoxee.instance().isReady()) {
51
- TimeUnit.MILLISECONDS.sleep(300);
52
- limit--;
53
- }
54
- } catch (Exception e) {
55
- Log.e("MessageService", "waitInitialization interrupted", e);
56
- }
57
- }
58
38
  }