From a7764d8adf333a6bc8ee0c42bf8078b3c708d443 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Mon, 6 May 2024 09:47:51 -0700 Subject: [PATCH 1/2] Add a supervisorjob and exception hander to UIService Currently AEPUIService creates a coroutine scope for every presentable to avoid one workflow distuption cancelling another. Instead we can use a combination of SupervisorJob and a Coroutine execption handler to use the same coroutine scope for all operations on main thread by UIService, while ensuring that the disjoint interactions do not interfere with each other. --- .../mobile/services/ui/AEPUIService.kt | 29 +++++++++++++++++-- .../services/ui/alert/AlertPresentable.kt | 7 +++-- .../services/ui/common/AEPPresentable.kt | 8 ++--- .../FloatingButtonPresentable.kt | 7 +++-- .../ui/message/InAppMessagePresentable.kt | 3 +- .../services/ui/alert/AlertPresentableTest.kt | 6 +++- .../FloatingButtonPresentableTest.kt | 7 ++++- 7 files changed, 53 insertions(+), 14 deletions(-) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPUIService.kt b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPUIService.kt index 8e72fb95c..f92f30cd6 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPUIService.kt +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/AEPUIService.kt @@ -12,20 +12,41 @@ package com.adobe.marketing.mobile.services.ui import android.app.Application +import com.adobe.marketing.mobile.services.Log +import com.adobe.marketing.mobile.services.ServiceConstants import com.adobe.marketing.mobile.services.ui.alert.AlertPresentable import com.adobe.marketing.mobile.services.ui.common.AppLifecycleProvider import com.adobe.marketing.mobile.services.ui.floatingbutton.FloatingButtonPresentable import com.adobe.marketing.mobile.services.ui.floatingbutton.FloatingButtonViewModel import com.adobe.marketing.mobile.services.ui.message.InAppMessagePresentable +import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob /** * UI Service implementation for AEP SDK */ internal class AEPUIService : UIService { + companion object { + private const val LOG_TAG = "AEPUIService" + } + private var presentationDelegate: PresentationDelegate? = null + private val exceptionHandler = CoroutineExceptionHandler { _, throwable -> + Log.error( + ServiceConstants.LOG_TAG, + LOG_TAG, + "An error occurred while processing the presentation: ${throwable.message}", + throwable + ) + } + + private val mainScope by lazy { + CoroutineScope(Dispatchers.Main + SupervisorJob() + exceptionHandler) + } + @Suppress("UNCHECKED_CAST") override fun > create( presentation: T, @@ -44,7 +65,7 @@ internal class AEPUIService : UIService { presentationDelegate, presentationUtilityProvider, AppLifecycleProvider.INSTANCE, - CoroutineScope(Dispatchers.Main) + mainScope ) as Presentable } @@ -53,7 +74,8 @@ internal class AEPUIService : UIService { presentation, presentationDelegate, presentationUtilityProvider, - AppLifecycleProvider.INSTANCE + AppLifecycleProvider.INSTANCE, + mainScope ) as Presentable } @@ -63,7 +85,8 @@ internal class AEPUIService : UIService { FloatingButtonViewModel(presentation.settings), presentationDelegate, presentationUtilityProvider, - AppLifecycleProvider.INSTANCE + AppLifecycleProvider.INSTANCE, + mainScope ) as Presentable } diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/alert/AlertPresentable.kt b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/alert/AlertPresentable.kt index 981a72a0b..1ff171fce 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/alert/AlertPresentable.kt +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/alert/AlertPresentable.kt @@ -21,6 +21,7 @@ import com.adobe.marketing.mobile.services.ui.PresentationUtilityProvider import com.adobe.marketing.mobile.services.ui.alert.views.AlertScreen import com.adobe.marketing.mobile.services.ui.common.AEPPresentable import com.adobe.marketing.mobile.services.ui.common.AppLifecycleProvider +import kotlinx.coroutines.CoroutineScope /** * Represents an Alert presentable. @@ -33,12 +34,14 @@ internal class AlertPresentable( val alert: Alert, presentationDelegate: PresentationDelegate?, presentationUtilityProvider: PresentationUtilityProvider, - appLifecycleProvider: AppLifecycleProvider + appLifecycleProvider: AppLifecycleProvider, + mainScope: CoroutineScope ) : AEPPresentable( alert, presentationUtilityProvider, presentationDelegate, - appLifecycleProvider + appLifecycleProvider, + mainScope ) { override fun getContent(activityContext: Context): ComposeView { return ComposeView(activityContext).apply { diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentable.kt b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentable.kt index 42ed3002b..ec83e8abf 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentable.kt +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentable.kt @@ -33,7 +33,6 @@ import com.adobe.marketing.mobile.services.ui.Presentation import com.adobe.marketing.mobile.services.ui.PresentationDelegate import com.adobe.marketing.mobile.services.ui.PresentationUtilityProvider import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.lang.ref.WeakReference import java.util.Random @@ -52,10 +51,10 @@ internal abstract class AEPPresentable> : private val presentation: Presentation private val presentationUtilityProvider: PresentationUtilityProvider private val presentationDelegate: PresentationDelegate? - private val mainScope: CoroutineScope private val appLifecycleProvider: AppLifecycleProvider private val presentationObserver: PresentationObserver private val activityCompatOwnerUtils: ActivityCompatOwnerUtils + protected val mainScope: CoroutineScope protected val presentationStateManager: PresentationStateManager @VisibleForTesting internal val contentIdentifier: Int = Random().nextInt() @@ -70,7 +69,8 @@ internal abstract class AEPPresentable> : presentation: Presentation, presentationUtilityProvider: PresentationUtilityProvider, presentationDelegate: PresentationDelegate?, - appLifecycleProvider: AppLifecycleProvider + appLifecycleProvider: AppLifecycleProvider, + mainScope: CoroutineScope ) : this( presentation, presentationUtilityProvider, @@ -78,7 +78,7 @@ internal abstract class AEPPresentable> : appLifecycleProvider, PresentationStateManager(), ActivityCompatOwnerUtils(), - CoroutineScope(Dispatchers.Main), + mainScope, PresentationObserver.INSTANCE ) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/floatingbutton/FloatingButtonPresentable.kt b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/floatingbutton/FloatingButtonPresentable.kt index 02685a3d7..0826a0de8 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/floatingbutton/FloatingButtonPresentable.kt +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/floatingbutton/FloatingButtonPresentable.kt @@ -21,6 +21,7 @@ import com.adobe.marketing.mobile.services.ui.PresentationUtilityProvider import com.adobe.marketing.mobile.services.ui.common.AEPPresentable import com.adobe.marketing.mobile.services.ui.common.AppLifecycleProvider import com.adobe.marketing.mobile.services.ui.floatingbutton.views.FloatingButtonScreen +import kotlinx.coroutines.CoroutineScope /** * Represents a presentable floating button presentation @@ -35,12 +36,14 @@ internal class FloatingButtonPresentable( private val floatingButtonViewModel: FloatingButtonViewModel, presentationDelegate: PresentationDelegate?, presentationUtilityProvider: PresentationUtilityProvider, - appLifecycleProvider: AppLifecycleProvider + appLifecycleProvider: AppLifecycleProvider, + mainScope: CoroutineScope ) : AEPPresentable( floatingButton, presentationUtilityProvider, presentationDelegate, - appLifecycleProvider + appLifecycleProvider, + mainScope ) { // event handler for the floating button private val floatingButtonEventHandler = object : FloatingButtonEventHandler { diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/message/InAppMessagePresentable.kt b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/message/InAppMessagePresentable.kt index 4d4cb8a2a..2ece6ac20 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/message/InAppMessagePresentable.kt +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/message/InAppMessagePresentable.kt @@ -45,7 +45,8 @@ internal class InAppMessagePresentable( inAppMessage, presentationUtilityProvider, presentationDelegate, - appLifecycleProvider + appLifecycleProvider, + mainScope ) { companion object { diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/alert/AlertPresentableTest.kt b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/alert/AlertPresentableTest.kt index 0b2d51a75..b6d93753a 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/alert/AlertPresentableTest.kt +++ b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/alert/AlertPresentableTest.kt @@ -17,6 +17,7 @@ import com.adobe.marketing.mobile.services.ui.InAppMessage import com.adobe.marketing.mobile.services.ui.PresentationDelegate import com.adobe.marketing.mobile.services.ui.PresentationUtilityProvider import com.adobe.marketing.mobile.services.ui.common.AppLifecycleProvider +import kotlinx.coroutines.CoroutineScope import org.junit.Before import org.junit.Test import org.mockito.Mock @@ -40,12 +41,15 @@ class AlertPresentableTest { @Mock private lateinit var mockAppLifecycleProvider: AppLifecycleProvider + @Mock + private lateinit var mockScope: CoroutineScope + private lateinit var alertPresentable: AlertPresentable @Before fun setUp() { MockitoAnnotations.openMocks(this) - alertPresentable = AlertPresentable(mockAlert, mockPresentationDelegate, mockPresentationUtilityProvider, mockAppLifecycleProvider) + alertPresentable = AlertPresentable(mockAlert, mockPresentationDelegate, mockPresentationUtilityProvider, mockAppLifecycleProvider, mockScope) } @Test diff --git a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/floatingbutton/FloatingButtonPresentableTest.kt b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/floatingbutton/FloatingButtonPresentableTest.kt index 298cf4dc3..c5813d5ad 100644 --- a/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/floatingbutton/FloatingButtonPresentableTest.kt +++ b/code/core/src/test/java/com/adobe/marketing/mobile/services/ui/floatingbutton/FloatingButtonPresentableTest.kt @@ -17,6 +17,7 @@ import com.adobe.marketing.mobile.services.ui.InAppMessage import com.adobe.marketing.mobile.services.ui.PresentationDelegate import com.adobe.marketing.mobile.services.ui.PresentationUtilityProvider import com.adobe.marketing.mobile.services.ui.common.AppLifecycleProvider +import kotlinx.coroutines.CoroutineScope import org.junit.Before import org.junit.Test import org.mockito.Mock @@ -47,6 +48,9 @@ class FloatingButtonPresentableTest { @Mock private lateinit var mockFloatingButtonSettings: FloatingButtonSettings + @Mock + private lateinit var mockScope: CoroutineScope + private lateinit var floatingButtonPresentable: FloatingButtonPresentable @Before @@ -61,7 +65,8 @@ class FloatingButtonPresentableTest { mockFloatingButtonViewModel, mockPresentationDelegate, mockPresentationUtilityProvider, - mockAppLifecycleProvider + mockAppLifecycleProvider, + mockScope ) verify(mockFloatingButtonViewModel).onGraphicUpdate(mockFloatingButtonSettings.initialGraphic) From 330dd0726a8d1b7664429351298290296b8ea920 Mon Sep 17 00:00:00 2001 From: Prashanth Rudrabhat Date: Tue, 7 May 2024 13:25:52 -0700 Subject: [PATCH 2/2] mainScope need not be protected --- .../adobe/marketing/mobile/services/ui/common/AEPPresentable.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentable.kt b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentable.kt index ec83e8abf..e7b7a4910 100644 --- a/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentable.kt +++ b/code/core/src/phone/java/com/adobe/marketing/mobile/services/ui/common/AEPPresentable.kt @@ -54,7 +54,7 @@ internal abstract class AEPPresentable> : private val appLifecycleProvider: AppLifecycleProvider private val presentationObserver: PresentationObserver private val activityCompatOwnerUtils: ActivityCompatOwnerUtils - protected val mainScope: CoroutineScope + private val mainScope: CoroutineScope protected val presentationStateManager: PresentationStateManager @VisibleForTesting internal val contentIdentifier: Int = Random().nextInt()