Skip to content

Commit d4f95d3

Browse files
authored
Add Live Update & fallback (#597)
1 parent 3a0ca80 commit d4f95d3

25 files changed

+734
-183
lines changed

android/gradle.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ Airship_minSdkVersion=21
33
Airship_targetSdkVersion=34
44
Airship_compileSdkVersion=34
55
Airship_ndkversion=26.1.10909125
6-
Airship_airshipProxyVersion=8.3.0
6+
Airship_airshipProxyVersion=9.1.3

android/src/main/java/com/urbanairship/reactnative/AirshipExtender.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.urbanairship.UAirship
1010
* Register the extender fully qualified class name in the manifest under the key
1111
* `com.urbanairship.reactnative.AIRSHIP_EXTENDER`.
1212
*/
13-
interface AirshipExtender {
13+
@Deprecated("Use com.urbanairship.android.framework.proxy.AirshipPluginExtender instead and register it under the manifest key `com.urbanairship.plugin.extender`")
14+
interface AirshipExtender {
1415
fun onAirshipReady(context: Context, airship: UAirship)
1516
}

android/src/main/java/com/urbanairship/reactnative/AirshipModule.kt

+56-2
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ import com.urbanairship.PendingResult
88
import com.urbanairship.actions.ActionResult
99
import com.urbanairship.actions.ActionValue
1010
import com.urbanairship.android.framework.proxy.EventType
11+
import com.urbanairship.android.framework.proxy.NotificationConfig
1112
import com.urbanairship.android.framework.proxy.ProxyLogger
1213
import com.urbanairship.android.framework.proxy.events.EventEmitter
1314
import com.urbanairship.android.framework.proxy.proxies.AirshipProxy
15+
import com.urbanairship.android.framework.proxy.proxies.EnableUserNotificationsArgs
1416
import com.urbanairship.android.framework.proxy.proxies.FeatureFlagProxy
17+
import com.urbanairship.android.framework.proxy.proxies.LiveUpdateRequest
1518
import com.urbanairship.android.framework.proxy.proxies.SuspendingPredicate
1619
import com.urbanairship.json.JsonMap
1720
import com.urbanairship.json.JsonSerializable
@@ -238,9 +241,12 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) :
238241
}
239242

240243
@ReactMethod
241-
override fun pushEnableUserNotifications(promise: Promise) {
244+
override fun pushEnableUserNotifications(options: ReadableMap?, promise: Promise) {
242245
promise.resolveSuspending(scope) {
243-
proxy.push.enableUserPushNotifications()
246+
val args = options?.let {
247+
EnableUserNotificationsArgs.fromJson(Utils.convertMap(it).toJsonValue())
248+
}
249+
proxy.push.enableUserPushNotifications(args = args)
244250
}
245251
}
246252

@@ -693,6 +699,12 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) :
693699
}
694700
}
695701

702+
override fun liveActivityListAll(promise: Promise) {
703+
promise.resolveResult {
704+
throw IllegalStateException("Not supported on Android")
705+
}
706+
}
707+
696708
override fun liveActivityList(request: ReadableMap?, promise: Promise) {
697709
promise.resolveResult {
698710
throw IllegalStateException("Not supported on Android")
@@ -717,6 +729,48 @@ class AirshipModule internal constructor(val context: ReactApplicationContext) :
717729
}
718730
}
719731

732+
override fun liveUpdateListAll(promise: Promise) {
733+
promise.resolveSuspending(scope) {
734+
proxy.liveUpdateManager.listAll().let {
735+
JsonValue.wrapOpt(it)
736+
}
737+
}
738+
}
739+
740+
override fun liveUpdateList(request: ReadableMap?, promise: Promise) {
741+
promise.resolveSuspending(scope) {
742+
proxy.liveUpdateManager.list(
743+
LiveUpdateRequest.List.fromJson(Utils.convertMap(requireNotNull(request)).toJsonValue())
744+
).let {
745+
JsonValue.wrapOpt(it)
746+
}
747+
}
748+
}
749+
750+
override fun liveUpdateCreate(request: ReadableMap?, promise: Promise) {
751+
promise.resolveSuspending(scope) {
752+
proxy.liveUpdateManager.create(
753+
LiveUpdateRequest.Create.fromJson(Utils.convertMap(requireNotNull(request)).toJsonValue())
754+
)
755+
}
756+
}
757+
758+
override fun liveUpdateUpdate(request: ReadableMap?, promise: Promise) {
759+
promise.resolveSuspending(scope) {
760+
proxy.liveUpdateManager.update(
761+
LiveUpdateRequest.Update.fromJson(Utils.convertMap(requireNotNull(request)).toJsonValue())
762+
)
763+
}
764+
}
765+
766+
override fun liveUpdateEnd(request: ReadableMap?, promise: Promise) {
767+
promise.resolveSuspending(scope) {
768+
proxy.liveUpdateManager.end(
769+
LiveUpdateRequest.End.fromJson(Utils.convertMap(requireNotNull(request)).toJsonValue())
770+
)
771+
}
772+
}
773+
720774
private fun notifyPending() {
721775
if (context.hasActiveReactInstance()) {
722776
val appEventEmitter = context.getJSModule(RCTNativeAppEventEmitter::class.java)

android/src/main/java/com/urbanairship/reactnative/ReactAutopilot.kt

+11-9
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import com.urbanairship.embedded.AirshipEmbeddedInfo
1717
import com.urbanairship.embedded.AirshipEmbeddedObserver
1818
import com.urbanairship.json.JsonMap
1919
import com.urbanairship.json.jsonMapOf
20-
import kotlinx.coroutines.MainScope
20+
import kotlinx.coroutines.CoroutineScope
21+
import kotlinx.coroutines.Dispatchers
22+
import kotlinx.coroutines.SupervisorJob
2123
import kotlinx.coroutines.flow.filter
2224
import kotlinx.coroutines.launch
2325

@@ -26,33 +28,32 @@ import kotlinx.coroutines.launch
2628
*/
2729
class ReactAutopilot : BaseAutopilot() {
2830

29-
override fun onAirshipReady(airship: UAirship) {
30-
super.onAirshipReady(airship)
31+
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
3132

33+
override fun onReady(context: Context, airship: UAirship) {
3234
ProxyLogger.info("Airship React Native version: %s, SDK version: %s", BuildConfig.AIRSHIP_MODULE_VERSION, UAirship.getVersion())
3335

34-
val context = UAirship.getApplicationContext()
35-
36-
MainScope().launch {
36+
scope.launch {
3737
EventEmitter.shared().pendingEventListener
3838
.filter { !it.type.isForeground() }
3939
.collect {
4040
AirshipHeadlessEventService.startService(context)
4141
}
4242
}
4343

44-
MainScope().launch {
44+
scope.launch {
4545
AirshipEmbeddedObserver(filter = { true }).embeddedViewInfoFlow.collect {
4646
EventEmitter.shared().addEvent(PendingEmbeddedUpdated(it))
4747
}
4848
}
4949

50-
// Set our custom notification providerr
50+
// Set our custom notification provider
5151
val notificationProvider = ReactNotificationProvider(context, airship.airshipConfigOptions)
5252
airship.pushManager.notificationProvider = notificationProvider
5353

5454
airship.analytics.registerSDKExtension(Extension.REACT_NATIVE, BuildConfig.AIRSHIP_MODULE_VERSION)
5555

56+
// Legacy extender
5657
val extender = createExtender(context)
5758
extender?.onAirshipReady(context, airship)
5859
}
@@ -61,6 +62,7 @@ class ReactAutopilot : BaseAutopilot() {
6162
DataMigrator(context).migrateData(proxyStore)
6263
}
6364

65+
@Suppress("deprecation")
6466
private fun createExtender(context: Context): AirshipExtender? {
6567
val ai: ApplicationInfo
6668
try {
@@ -77,7 +79,7 @@ class ReactAutopilot : BaseAutopilot() {
7779

7880
try {
7981
val extenderClass = Class.forName(classname)
80-
return extenderClass.newInstance() as AirshipExtender
82+
return extenderClass.getDeclaredConstructor().newInstance() as AirshipExtender
8183
} catch (e: Exception) {
8284
ProxyLogger.error(e, "Unable to create extender: $classname")
8385
}

android/src/oldarch/java/com/urbanairship/reactnative/AirshipSpec.kt

+24-2
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ abstract class AirshipSpec internal constructor(context: ReactApplicationContext
9898

9999
@ReactMethod
100100
@com.facebook.proguard.annotations.DoNotStrip
101-
abstract fun pushEnableUserNotifications(promise: Promise)
101+
abstract fun pushEnableUserNotifications(options: ReadableMap?, promise: Promise)
102102

103103
@ReactMethod
104104
@com.facebook.proguard.annotations.DoNotStrip
@@ -405,6 +405,10 @@ abstract class AirshipSpec internal constructor(context: ReactApplicationContext
405405
@com.facebook.proguard.annotations.DoNotStrip
406406
abstract fun featureFlagManagerTrackInteraction(flag: ReadableMap?, promise: Promise)
407407

408+
@ReactMethod
409+
@com.facebook.proguard.annotations.DoNotStrip
410+
abstract fun liveActivityListAll(promise: Promise)
411+
408412
@ReactMethod
409413
@com.facebook.proguard.annotations.DoNotStrip
410414
abstract fun liveActivityList(request: ReadableMap?, promise: Promise)
@@ -420,6 +424,24 @@ abstract class AirshipSpec internal constructor(context: ReactApplicationContext
420424
@ReactMethod
421425
@com.facebook.proguard.annotations.DoNotStrip
422426
abstract fun liveActivityEnd(request: ReadableMap?, promise: Promise)
423-
}
424427

428+
@ReactMethod
429+
@com.facebook.proguard.annotations.DoNotStrip
430+
abstract fun liveUpdateListAll(promise: Promise)
431+
432+
@ReactMethod
433+
@com.facebook.proguard.annotations.DoNotStrip
434+
abstract fun liveUpdateList(request: ReadableMap?, promise: Promise)
435+
436+
@ReactMethod
437+
@com.facebook.proguard.annotations.DoNotStrip
438+
abstract fun liveUpdateCreate(request: ReadableMap?, promise: Promise)
425439

440+
@ReactMethod
441+
@com.facebook.proguard.annotations.DoNotStrip
442+
abstract fun liveUpdateUpdate(request: ReadableMap?, promise: Promise)
443+
444+
@ReactMethod
445+
@com.facebook.proguard.annotations.DoNotStrip
446+
abstract fun liveUpdateEnd(request: ReadableMap?, promise: Promise)
447+
}

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

+22-17
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,28 @@
33
<uses-permission android:name="android.permission.INTERNET" />
44

55
<application
6-
android:name=".MainApplication"
7-
android:label="@string/app_name"
8-
android:icon="@mipmap/ic_launcher"
9-
android:roundIcon="@mipmap/ic_launcher_round"
10-
android:allowBackup="false"
11-
android:theme="@style/AppTheme">
12-
<activity
13-
android:name=".MainActivity"
6+
android:name=".MainApplication"
7+
android:allowBackup="false"
8+
android:icon="@mipmap/ic_launcher"
149
android:label="@string/app_name"
15-
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
16-
android:launchMode="singleTask"
17-
android:windowSoftInputMode="adjustResize"
18-
android:exported="true">
19-
<intent-filter>
20-
<action android:name="android.intent.action.MAIN" />
21-
<category android:name="android.intent.category.LAUNCHER" />
22-
</intent-filter>
23-
</activity>
10+
android:roundIcon="@mipmap/ic_launcher_round"
11+
android:theme="@style/AppTheme">
12+
13+
<meta-data
14+
android:name="com.urbanairship.plugin.extender"
15+
android:value="com.urbanairship.sample.AirshipExtender" />
16+
17+
<activity
18+
android:name=".MainActivity"
19+
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
20+
android:exported="true"
21+
android:label="@string/app_name"
22+
android:launchMode="singleTask"
23+
android:windowSoftInputMode="adjustResize">
24+
<intent-filter>
25+
<action android:name="android.intent.action.MAIN" />
26+
<category android:name="android.intent.category.LAUNCHER" />
27+
</intent-filter>
28+
</activity>
2429
</application>
2530
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.urbanairship.sample
2+
3+
import android.app.NotificationChannel
4+
import android.app.NotificationManager
5+
import android.app.PendingIntent
6+
import android.content.Context
7+
import android.content.Intent
8+
import android.os.Build
9+
import androidx.annotation.Keep
10+
import androidx.core.app.NotificationCompat
11+
import androidx.core.app.NotificationManagerCompat
12+
import com.urbanairship.UAirship
13+
import com.urbanairship.android.framework.proxy.AirshipPluginExtender
14+
import com.urbanairship.json.requireField
15+
import com.urbanairship.liveupdate.LiveUpdate
16+
import com.urbanairship.liveupdate.LiveUpdateEvent
17+
import com.urbanairship.liveupdate.LiveUpdateManager
18+
import com.urbanairship.liveupdate.LiveUpdateResult
19+
import com.urbanairship.liveupdate.SuspendLiveUpdateNotificationHandler
20+
21+
22+
@Keep
23+
public final class AirshipExtender: AirshipPluginExtender {
24+
override fun onAirshipReady(context: Context, airship: UAirship) {
25+
LiveUpdateManager.shared().register("Example", ExampleLiveUpdateHandler())
26+
}
27+
}
28+
29+
public final class ExampleLiveUpdateHandler: SuspendLiveUpdateNotificationHandler() {
30+
override suspend fun onUpdate(
31+
context: Context,
32+
event: LiveUpdateEvent,
33+
update: LiveUpdate
34+
): LiveUpdateResult<NotificationCompat.Builder> {
35+
36+
if (event == LiveUpdateEvent.END) {
37+
// Dismiss the live update on END. The default behavior will leave the Live Update
38+
// in the notification tray until the dismissal time is reached or the user dismisses it.
39+
return LiveUpdateResult.cancel()
40+
}
41+
42+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
43+
val importance = NotificationManager.IMPORTANCE_DEFAULT
44+
val channel = NotificationChannel("emoji-example", "Emoji example", importance)
45+
channel.description = "Emoji example"
46+
NotificationManagerCompat.from(context).createNotificationChannel(channel)
47+
}
48+
49+
val launchIntent = context.packageManager
50+
.getLaunchIntentForPackage(context.packageName)
51+
?.addCategory(update.name)
52+
?.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
53+
?.setPackage(null)
54+
55+
val contentIntent = PendingIntent.getActivity(
56+
context, 0, launchIntent, PendingIntent.FLAG_IMMUTABLE
57+
)
58+
59+
val notification = NotificationCompat.Builder(context, "emoji-example")
60+
.setSmallIcon(R.drawable.ic_notification)
61+
.setPriority(NotificationCompat.PRIORITY_MAX)
62+
.setCategory(NotificationCompat.CATEGORY_EVENT)
63+
.setContentTitle("Example Live Update")
64+
.setContentText(update.content.requireField<String>("emoji"))
65+
.setContentIntent(contentIntent)
66+
67+
return LiveUpdateResult.ok(notification)
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="24dp"
3+
android:height="24dp"
4+
android:viewportWidth="24"
5+
android:viewportHeight="24"
6+
android:tint="#FFFFFF">
7+
<group android:scaleX="0.92"
8+
android:scaleY="0.92"
9+
android:translateX="0.96"
10+
android:translateY="0.96">
11+
<path
12+
android:pathData="M17.6,11.48 L19.44,8.3a0.63,0.63 0,0 0,-1.09 -0.63l-1.88,3.24a11.43,11.43 0,0 0,-8.94 0L5.65,7.67a0.63,0.63 0,0 0,-1.09 0.63L6.4,11.48A10.81,10.81 0,0 0,1 20L23,20A10.81,10.81 0,0 0,17.6 11.48ZM7,17.25A1.25,1.25 0,1 1,8.25 16,1.25 1.25,0 0,1 7,17.25ZM17,17.25A1.25,1.25 0,1 1,18.25 16,1.25 1.25,0 0,1 17,17.25Z"
13+
android:fillColor="#FF000000"/>
14+
</group>
15+
</vector>
Loading
Loading
Loading
Loading

example/ios/Podfile.lock

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ PODS:
1717
- Airship/Core
1818
- Airship/PreferenceCenter (18.9.2):
1919
- Airship/Core
20-
- AirshipFrameworkProxy (8.3.0):
20+
- AirshipFrameworkProxy (9.1.3):
2121
- Airship (= 18.9.2)
2222
- AirshipServiceExtension (18.9.2)
2323
- boost (1.83.0)
@@ -908,7 +908,7 @@ PODS:
908908
- glog
909909
- React-debug
910910
- react-native-airship (19.3.2):
911-
- AirshipFrameworkProxy (= 8.3.0)
911+
- AirshipFrameworkProxy (= 9.1.3)
912912
- glog
913913
- RCT-Folly (= 2022.05.16.00)
914914
- React-Core
@@ -1280,7 +1280,7 @@ EXTERNAL SOURCES:
12801280

12811281
SPEC CHECKSUMS:
12821282
Airship: 7f891aa9bb142d02f35aaef5ebdb09c2b5730a6d
1283-
AirshipFrameworkProxy: 48208d21ca1376d9bc111efd31981af84bfcf8f3
1283+
AirshipFrameworkProxy: 9a983b72a47ce10d8eda32b446ea553ef7bcc8f2
12841284
AirshipServiceExtension: 0ed795b521a76f8391e13896fbe1dee6ce9196ca
12851285
boost: d3f49c53809116a5d38da093a8aa78bf551aed09
12861286
DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953
@@ -1311,7 +1311,7 @@ SPEC CHECKSUMS:
13111311
React-jsinspector: 9ac353eccf6ab54d1e0a33862ba91221d1e88460
13121312
React-logger: 0a57b68dd2aec7ff738195f081f0520724b35dab
13131313
React-Mapbuffer: 63913773ed7f96b814a2521e13e6d010282096ad
1314-
react-native-airship: c68835c32f7b5a100b25bf5f2ecc6e4fae6ddce4
1314+
react-native-airship: 6afeeef72fa57a06a716afaca0ababb8adfaee81
13151315
react-native-safe-area-context: b97eb6f9e3b7f437806c2ce5983f479f8eb5de4b
13161316
React-nativeconfig: d7af5bae6da70fa15ce44f045621cf99ed24087c
13171317
React-NativeModulesApple: 0123905d5699853ac68519607555a9a4f5c7b3ac

0 commit comments

Comments
 (0)