diff --git a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/model/MainActivityViewModel.java b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/model/MainActivityViewModel.java index a4412cd719..04a51fd83a 100644 --- a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/model/MainActivityViewModel.java +++ b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/model/MainActivityViewModel.java @@ -16,6 +16,7 @@ import android.content.Intent; import android.os.Build; +import android.util.Log; import android.util.Pair; import android.view.View; import android.view.ViewTreeObserver; @@ -54,14 +55,15 @@ import com.onesignal.sdktest.util.ProfileUtil; import com.onesignal.sdktest.util.Toaster; import com.onesignal.user.subscriptions.ISubscription; -import com.onesignal.user.subscriptions.ISubscriptionChangedHandler; +import com.onesignal.user.subscriptions.IPushSubscriptionObserver; +import com.onesignal.user.subscriptions.PushSubscriptionChangedState; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @RequiresApi(api = Build.VERSION_CODES.N) -public class MainActivityViewModel implements ActivityViewModel, ISubscriptionChangedHandler { +public class MainActivityViewModel implements ActivityViewModel, IPushSubscriptionObserver { private Animate animate; private Dialog dialog; @@ -290,7 +292,7 @@ public ActivityViewModel onActivityCreated(Context context) { triggerSet = new HashMap<>(); triggerArrayList = new ArrayList<>(); - OneSignal.getUser().getPushSubscription().addChangeHandler(this); + OneSignal.getUser().getPushSubscription().addObserver(this); return this; } @@ -494,6 +496,11 @@ public void run() { }); } + @Override + public void onPushSubscriptionChange(@NonNull PushSubscriptionChangedState state) { + refreshSubscriptionState(); + } + private class DummySubscription implements ISubscription { private String _id; @@ -506,16 +513,6 @@ public DummySubscription(String id) { public String getId() { return _id; } - - @Override - public void addChangeHandler(@NonNull ISubscriptionChangedHandler handler) { - - } - - @Override - public void removeChangeHandler(@NonNull ISubscriptionChangedHandler handler) { - - } } private void setupEmailLayout() { @@ -825,19 +822,6 @@ private void setupPromptPushButton() { }); } - @Override - public void onSubscriptionChanged(@NonNull ISubscription subscription) { - if(subscription instanceof IPushSubscription) { - refreshSubscriptionState(); - } - else if(subscription instanceof IEmailSubscription) { - refreshEmailRecyclerView(); - } - else if(subscription instanceof ISmsSubscription) { - refreshSMSRecyclerView(); - } - } - private void refreshSubscriptionState() { boolean isPermissionEnabled = OneSignal.getNotifications().getPermission(); IPushSubscription pushSubscription = OneSignal.getUser().getPushSubscription(); diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 6e77797802..ab2e2e1205 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -149,6 +149,34 @@ To resume receiving of push notifications (driving the native permission prompt pushSubscription.optIn() +To observe changes to the push subscription a class can implement the IPushSubscriptionObserver interface, and can then be added as an observer: + +**Java** + + @Override + public void onPushSubscriptionChange(@NonNull PushSubscriptionChangedState state) { + ... + } + + pushSubscription.addObserver(this); + +**Kotlin** + + override fun onPushSubscriptionChange(state: PushSubscriptionChangedState) { + ... + } + + pushSubscription.addObserver(this) + +If you would like to stop observing the subscription you can remove the observer: + +**Java** + + pushSubscription.removeObserver(this); + +**Kotlin** + + pushSubscription.removeObserver(this) **Email/SMS Subscriptions** Email and/or SMS subscriptions can be added or removed via: @@ -186,12 +214,12 @@ The OneSignal SDK has been rewritten in Kotlin. This is typically transparent to In Java this is surfaced on a method via an additional parameter to the method of type `Continuation`. The `Continuation` is a callback mechanism which allows a Java function to gain control when execution has resumed. If this concept is newer to your application codebase, OneSignal provides an optional java helper class to facilitate the callback model. Method `com.onesignal.Continue.none()` can be used to indicate no callback is necessary: - OneSignal.getNotifications().requestPermission(Continue.none()); + OneSignal.getNotifications().requestPermission(true, Continue.none()); `com.onesignal.Continue.with()` can be used to create a callback lambda expression, which is passed a `ContinueResult` containing information on the success of the underlying coroutine, it's return data, and/or the exception that was thrown: - OneSignal.getNotifications().requestPermission(Continue.with(r -> { + OneSignal.getNotifications().requestPermission(true, Continue.with(r -> { if (r.isSuccess()) { if (r.getData()) { // code to execute once requestPermission has completed successfully and the user has accepted permission. diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/PushSubscription.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/PushSubscription.kt index d506d9f172..24ae03a251 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/PushSubscription.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/PushSubscription.kt @@ -1,13 +1,19 @@ package com.onesignal.user.internal +import com.onesignal.common.events.EventProducer import com.onesignal.user.internal.subscriptions.SubscriptionModel import com.onesignal.user.internal.subscriptions.SubscriptionStatus import com.onesignal.user.internal.subscriptions.SubscriptionType import com.onesignal.user.subscriptions.IPushSubscription +import com.onesignal.user.subscriptions.IPushSubscriptionObserver +import com.onesignal.user.subscriptions.PushSubscriptionState internal open class PushSubscription( model: SubscriptionModel, ) : Subscription(model), IPushSubscription { + val changeHandlersNotifier = EventProducer() + var savedState = fetchState() + private set; override val token: String get() = model.address @@ -24,6 +30,18 @@ internal open class PushSubscription( override fun optOut() { model.optedIn = false } + + override fun addObserver(observer: IPushSubscriptionObserver) = changeHandlersNotifier.subscribe(observer) + override fun removeObserver(observer: IPushSubscriptionObserver) = changeHandlersNotifier.unsubscribe(observer) + + fun refreshState() : PushSubscriptionState { + savedState = fetchState() + return savedState + } + + private fun fetchState() : PushSubscriptionState { + return PushSubscriptionState(id, token, optedIn) + } } internal class UninitializedPushSubscription() : PushSubscription(createFakePushSub()) { diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/Subscription.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/Subscription.kt index 95c77cfcdb..ace7f0f104 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/Subscription.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/Subscription.kt @@ -4,7 +4,7 @@ import com.onesignal.common.IDManager import com.onesignal.common.events.EventProducer import com.onesignal.user.internal.subscriptions.SubscriptionModel import com.onesignal.user.subscriptions.ISubscription -import com.onesignal.user.subscriptions.ISubscriptionChangedHandler +import com.onesignal.user.subscriptions.IPushSubscriptionObserver /** * An abstract subscription represents an open channel between @@ -13,11 +13,5 @@ import com.onesignal.user.subscriptions.ISubscriptionChangedHandler internal abstract class Subscription( val model: SubscriptionModel, ) : ISubscription { - - val changeHandlersNotifier = EventProducer() - override val id: String get() = if (IDManager.isLocalId(model.id)) "" else model.id - - override fun addChangeHandler(handler: ISubscriptionChangedHandler) = changeHandlersNotifier.subscribe(handler) - override fun removeChangeHandler(handler: ISubscriptionChangedHandler) = changeHandlersNotifier.unsubscribe(handler) } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/UserManager.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/UserManager.kt index 42b9b5038d..c731947c57 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/UserManager.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/UserManager.kt @@ -12,7 +12,7 @@ import com.onesignal.user.internal.properties.PropertiesModel import com.onesignal.user.internal.properties.PropertiesModelStore import com.onesignal.user.internal.subscriptions.ISubscriptionManager import com.onesignal.user.subscriptions.IPushSubscription -import com.onesignal.user.subscriptions.SubscriptionList +import com.onesignal.user.internal.subscriptions.SubscriptionList internal open class UserManager( private val _subscriptionManager: ISubscriptionManager, diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/subscriptions/ISubscriptionManager.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/subscriptions/ISubscriptionManager.kt index d45894a909..0316049bef 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/subscriptions/ISubscriptionManager.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/subscriptions/ISubscriptionManager.kt @@ -3,7 +3,6 @@ package com.onesignal.user.internal.subscriptions import com.onesignal.common.events.IEventNotifier import com.onesignal.common.modeling.ModelChangedArgs import com.onesignal.user.subscriptions.ISubscription -import com.onesignal.user.subscriptions.SubscriptionList interface ISubscriptionManager : IEventNotifier { var subscriptions: SubscriptionList diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/SubscriptionList.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/subscriptions/SubscriptionList.kt similarity index 82% rename from OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/SubscriptionList.kt rename to OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/subscriptions/SubscriptionList.kt index 62a0820844..d0769e9f8b 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/SubscriptionList.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/subscriptions/SubscriptionList.kt @@ -1,4 +1,9 @@ -package com.onesignal.user.subscriptions +package com.onesignal.user.internal.subscriptions + +import com.onesignal.user.subscriptions.IEmailSubscription +import com.onesignal.user.subscriptions.IPushSubscription +import com.onesignal.user.subscriptions.ISmsSubscription +import com.onesignal.user.subscriptions.ISubscription /** * A readonly list of subscriptions. Wraps a standard [List] to help navigate the list of diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/subscriptions/impl/SubscriptionManager.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/subscriptions/impl/SubscriptionManager.kt index bad174d56c..a76fd78842 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/subscriptions/impl/SubscriptionManager.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/subscriptions/impl/SubscriptionManager.kt @@ -18,7 +18,8 @@ import com.onesignal.user.internal.subscriptions.SubscriptionModelStore import com.onesignal.user.internal.subscriptions.SubscriptionStatus import com.onesignal.user.internal.subscriptions.SubscriptionType import com.onesignal.user.subscriptions.ISubscription -import com.onesignal.user.subscriptions.SubscriptionList +import com.onesignal.user.internal.subscriptions.SubscriptionList +import com.onesignal.user.subscriptions.PushSubscriptionChangedState /** * The subscription manager is responsible for managing the external representation of the @@ -131,12 +132,15 @@ internal class SubscriptionManager( // don't yet have a representation for it in the subscription list. createSubscriptionAndAddToSubscriptionList(args.model as SubscriptionModel) } else { - (subscription as Subscription).changeHandlersNotifier.fireOnMain { - it.onSubscriptionChanged( - subscription, - ) + if (subscription is PushSubscription) { + subscription.changeHandlersNotifier.fireOnMain { + it.onPushSubscriptionChange( + PushSubscriptionChangedState( + subscription.savedState, + subscription.refreshState()) + ) + } } - // the model has already been updated, so fire the update event _events.fire { it.onSubscriptionChanged(subscription, args) } } @@ -161,7 +165,7 @@ internal class SubscriptionManager( // There can only be 1 push subscription, always transfer any subscribers from the old one to the new if (subscriptionModel.type == SubscriptionType.PUSH) { - val existingPushSubscription = this.subscriptions.push as Subscription + val existingPushSubscription = this.subscriptions.push as PushSubscription ((subscription as PushSubscription).changeHandlersNotifier).subscribeAll(existingPushSubscription.changeHandlersNotifier) subscriptions.remove(existingPushSubscription) } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/IPushSubscription.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/IPushSubscription.kt index 7c9ee5faac..1430141f96 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/IPushSubscription.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/IPushSubscription.kt @@ -5,7 +5,6 @@ package com.onesignal.user.subscriptions * channel. */ interface IPushSubscription : ISubscription { - /** * The token which identifies the device/app that notifications are to be sent. May * be an empty string, indicating the push token has not yet been retrieved. @@ -34,4 +33,15 @@ interface IPushSubscription : ISubscription { * notifications, although the app permission state will not be changed. */ fun optOut() + + /** + * Add an observer to this subscription, allowing the provider to be + * notified whenever the subscription has changed. + */ + fun addObserver(observer: IPushSubscriptionObserver) + + /** + * Remove an observer from this subscription. + */ + fun removeObserver(observer: IPushSubscriptionObserver) } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/ISubscriptionChangedHandler.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/IPushSubscriptionObserver.kt similarity index 58% rename from OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/ISubscriptionChangedHandler.kt rename to OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/IPushSubscriptionObserver.kt index 5aaa0a5d37..8a53eb8017 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/ISubscriptionChangedHandler.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/IPushSubscriptionObserver.kt @@ -2,16 +2,16 @@ package com.onesignal.user.subscriptions /** * A subscription changed handler. Implement this interface and provide the implementation - * to [ISubscription.addChangeHandler] to be notified when the subscription has changed. + * to [ISubscription.addObserver] to be notified when the subscription has changed. */ -interface ISubscriptionChangedHandler { +interface IPushSubscriptionObserver { /** * Called when the subscription this change handler was added to, has changed. A * subscription can change either because of a change driven by the application, or * by the backend. * - * @param subscription The subscription that has been changed. + * @param state The subscription changed state. */ - fun onSubscriptionChanged(subscription: ISubscription) + fun onPushSubscriptionChange(state: PushSubscriptionChangedState) } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/ISubscription.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/ISubscription.kt index 4b9ee8128f..4fd4d48bf6 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/ISubscription.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/ISubscription.kt @@ -11,15 +11,4 @@ interface ISubscription { * been successfully assigned. */ val id: String - - /** - * Add a change handler to this subscription, allowing the provider to be - * notified whenever the subscription has changed. - */ - fun addChangeHandler(handler: ISubscriptionChangedHandler) - - /** - * Remove a change handler from this subscription. - */ - fun removeChangeHandler(handler: ISubscriptionChangedHandler) } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/PushSubscriptionChangedState.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/PushSubscriptionChangedState.kt new file mode 100644 index 0000000000..c9fa171635 --- /dev/null +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/PushSubscriptionChangedState.kt @@ -0,0 +1,6 @@ +package com.onesignal.user.subscriptions + +class PushSubscriptionChangedState( + val previous: PushSubscriptionState, + val current: PushSubscriptionState +) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/PushSubscriptionState.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/PushSubscriptionState.kt new file mode 100644 index 0000000000..0fb5ae475c --- /dev/null +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/subscriptions/PushSubscriptionState.kt @@ -0,0 +1,28 @@ +package com.onesignal.user.subscriptions + +/** + * A subscription state. + */ +class PushSubscriptionState( + /** + * The unique identifier for this subscription. This will be an empty string + * until the subscription has been successfully created on the backend and + * assigned an ID. Use [addObserver] to be notified when the [id] has + * been successfully assigned. + */ + val id: String, + + /** + * The token which identifies the device/app that notifications are to be sent. May + * be an empty string, indicating the push token has not yet been retrieved. + */ + val token: String, + + /** + * Whether the user of this subscription is opted-in to received notifications. When true, + * the user is able to receive notifications through this subscription. Otherwise, the + * user will not receive notifications through this subscription (even when the user has + * granted app permission). + */ + val optedIn: Boolean +) diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/UserManagerTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/UserManagerTests.kt index 4bbf5b4c1f..b4c71bb962 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/UserManagerTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/UserManagerTests.kt @@ -3,7 +3,7 @@ package com.onesignal.user.internal import com.onesignal.core.internal.language.ILanguageContext import com.onesignal.mocks.MockHelper import com.onesignal.user.internal.subscriptions.ISubscriptionManager -import com.onesignal.user.subscriptions.SubscriptionList +import com.onesignal.user.internal.subscriptions.SubscriptionList import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import io.kotest.runner.junit4.KotestTestRunner