From bcef1f6ff292ae1d0c81a0ac5803a96776a4791a Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 21 Oct 2026 17:15:59 +0300 Subject: [PATCH 1/6] Rearrange `configChanges` flags in manifest --- sdk/src/main/AndroidManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/main/AndroidManifest.xml b/sdk/src/main/AndroidManifest.xml index 59aaab951..b318e16c0 100644 --- a/sdk/src/main/AndroidManifest.xml +++ b/sdk/src/main/AndroidManifest.xml @@ -13,7 +13,7 @@ @@ -39,7 +39,7 @@ From 812be295b8a3d4008089ea3e7fd762ecc249d86f Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 21 Oct 2026 17:24:00 +0300 Subject: [PATCH 2/6] Don't lock screen orientation on web auth activities --- .../ui/web/customtab/POCustomTabAuthorizationActivity.kt | 8 -------- .../sdk/ui/web/webview/POWebViewAuthorizationActivity.kt | 7 ------- 2 files changed, 15 deletions(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt index 738502c1a..6397f14ff 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt @@ -2,9 +2,7 @@ package com.processout.sdk.ui.web.customtab import android.app.Activity import android.content.Intent -import android.content.pm.ActivityInfo import android.net.Uri -import android.os.Build import android.os.Bundle import androidx.activity.addCallback import androidx.activity.viewModels @@ -61,12 +59,6 @@ class POCustomTabAuthorizationActivity : AppCompatActivity() { @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - // This is a workaround for crash on Android 8.0 (API level 26). - // https://issuetracker.google.com/issues/68454482 - // https://stackoverflow.com/questions/48072438/java-lang-illegalstateexception-only-fullscreen-opaque-activities-can-request-o - requestedOrientation = if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) - ActivityInfo.SCREEN_ORIENTATION_BEHIND else ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - onBackPressedDispatcher.addCallback(this) { // Ignore back press to avoid finishing activity without a result. // Cancelled result will be provided from onResume() when going back from the Custom Tab. diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt index 93a4ed1e1..917606b77 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt @@ -2,7 +2,6 @@ package com.processout.sdk.ui.web.webview import android.app.Activity import android.content.Intent -import android.content.pm.ActivityInfo import android.net.Uri import android.os.Build import android.os.Bundle @@ -33,12 +32,6 @@ class POWebViewAuthorizationActivity : AppCompatActivity() { @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - // This is a workaround for crash on Android 8.0 (API level 26). - // https://issuetracker.google.com/issues/68454482 - // https://stackoverflow.com/questions/48072438/java-lang-illegalstateexception-only-fullscreen-opaque-activities-can-request-o - requestedOrientation = if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) - ActivityInfo.SCREEN_ORIENTATION_BEHIND else ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - if (intent.getBooleanExtra(EXTRA_FORCE_FINISH, false)) { POLogger.info("Activity is started to clear the back stack and finished immediately before it's created.") finish() From 2f3c8b444132ec41f3ed55ebdd6f84805e5bd52a Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 21 Oct 2026 17:28:33 +0300 Subject: [PATCH 3/6] Remove redundant qualifier name --- .../ui/web/customtab/POCustomTabAuthorizationActivity.kt | 4 ++-- .../sdk/ui/web/webview/POWebViewAuthorizationActivity.kt | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt index 6397f14ff..092526f71 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/customtab/POCustomTabAuthorizationActivity.kt @@ -160,8 +160,8 @@ class POCustomTabAuthorizationActivity : AppCompatActivity() { result.onFailure { POLogger.info("Custom Chrome Tabs failure: %s", it) } when (configuration.resultApi) { Android -> when (result) { - is ProcessOutActivityResult.Success -> setActivityResult(Activity.RESULT_OK, result) - is ProcessOutActivityResult.Failure -> setActivityResult(Activity.RESULT_CANCELED, result) + is ProcessOutActivityResult.Success -> setActivityResult(RESULT_OK, result) + is ProcessOutActivityResult.Failure -> setActivityResult(RESULT_CANCELED, result) } Dispatcher -> resultDispatcher.dispatch(result) } diff --git a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt index 917606b77..1b43e43ba 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/ui/web/webview/POWebViewAuthorizationActivity.kt @@ -1,6 +1,5 @@ package com.processout.sdk.ui.web.webview -import android.app.Activity import android.content.Intent import android.net.Uri import android.os.Build @@ -80,8 +79,8 @@ class POWebViewAuthorizationActivity : AppCompatActivity() { if (!isFinishing) { when (configuration.resultApi) { Android -> when (result) { - is ProcessOutActivityResult.Success -> setActivityResult(Activity.RESULT_OK, result) - is ProcessOutActivityResult.Failure -> setActivityResult(Activity.RESULT_CANCELED, result) + is ProcessOutActivityResult.Success -> setActivityResult(RESULT_OK, result) + is ProcessOutActivityResult.Failure -> setActivityResult(RESULT_CANCELED, result) } Dispatcher -> resultDispatcher.dispatch(result) } @@ -97,7 +96,7 @@ class POWebViewAuthorizationActivity : AppCompatActivity() { super.finish() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { overrideActivityTransition( - Activity.OVERRIDE_TRANSITION_CLOSE, + OVERRIDE_TRANSITION_CLOSE, R.anim.po_slide_in_left, R.anim.po_slide_out_right ) From 7615e9d9bd8e44cac611985769b0ec323557b357 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 21 Oct 2026 17:49:00 +0300 Subject: [PATCH 4/6] Ensure that POCardTokenizationViewComponent uses the same event dispatcher instance as interactor --- .../ui/card/tokenization/CardTokenizationInteractor.kt | 2 +- .../ui/card/tokenization/CardTokenizationViewModel.kt | 2 ++ .../component/POCardTokenizationViewComponent.kt | 10 ++++------ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationInteractor.kt index f2dd1936d..fd00e1fa8 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationInteractor.kt @@ -60,7 +60,7 @@ internal class CardTokenizationInteractor( private val cardsRepository: POCardsRepository, private val cardSchemeProvider: CardSchemeProvider, private val addressSpecificationProvider: AddressSpecificationProvider, - private val eventDispatcher: POEventDispatcher + val eventDispatcher: POEventDispatcher ) : BaseInteractor() { private companion object { diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationViewModel.kt b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationViewModel.kt index b1783648b..124dbcc12 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationViewModel.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/CardTokenizationViewModel.kt @@ -74,6 +74,8 @@ internal class CardTokenizationViewModel private constructor( val sideEffects = interactor.sideEffects + val eventDispatcher = interactor.eventDispatcher + init { addCloseable(interactor.interactorScope) } diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/component/POCardTokenizationViewComponent.kt b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/component/POCardTokenizationViewComponent.kt index 2a395b794..e77838c31 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/component/POCardTokenizationViewComponent.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/component/POCardTokenizationViewComponent.kt @@ -71,12 +71,11 @@ class POCardTokenizationViewComponent private constructor( callback: (ProcessOutResult) -> Unit ): POCardTokenizationViewComponent { val configuration = configuration.map() - val eventDispatcher = POEventDispatcher() val viewModel: CardTokenizationViewModel by from.viewModels { CardTokenizationViewModel.Factory( app = from.requireActivity().application, configuration = configuration, - eventDispatcher = eventDispatcher + eventDispatcher = POEventDispatcher() ) } return POCardTokenizationViewComponent( @@ -96,7 +95,7 @@ class POCardTokenizationViewComponent private constructor( ), delegate = delegate, callback = callback, - eventDispatcher = eventDispatcher + eventDispatcher = viewModel.eventDispatcher ) } @@ -111,12 +110,11 @@ class POCardTokenizationViewComponent private constructor( callback: (ProcessOutResult) -> Unit ): POCardTokenizationViewComponent { val configuration = configuration.map() - val eventDispatcher = POEventDispatcher() val viewModel: CardTokenizationViewModel by from.viewModels { CardTokenizationViewModel.Factory( app = from.application, configuration = configuration, - eventDispatcher = eventDispatcher + eventDispatcher = POEventDispatcher() ) } return POCardTokenizationViewComponent( @@ -136,7 +134,7 @@ class POCardTokenizationViewComponent private constructor( ), delegate = delegate, callback = callback, - eventDispatcher = eventDispatcher + eventDispatcher = viewModel.eventDispatcher ) } From 261f9a0dd70de2306594c778fb0684b14c3098e7 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 21 Oct 2026 19:21:23 +0300 Subject: [PATCH 5/6] POCardTokenizationViewComponent: using correct coroutine scope --- .../POCardTokenizationViewComponent.kt | 75 ++++++++++--------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/component/POCardTokenizationViewComponent.kt b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/component/POCardTokenizationViewComponent.kt index e77838c31..ce1cb32b8 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/component/POCardTokenizationViewComponent.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/card/tokenization/component/POCardTokenizationViewComponent.kt @@ -37,6 +37,7 @@ import com.processout.sdk.ui.card.tokenization.screen.CardTokenizationScreen import com.processout.sdk.ui.core.theme.ProcessOutTheme import com.processout.sdk.ui.shared.configuration.POBottomSheetConfiguration import com.processout.sdk.ui.shared.configuration.POBottomSheetConfiguration.Height.WrapContent +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -50,15 +51,12 @@ class POCardTokenizationViewComponent private constructor( val view: View, private var configuration: POCardTokenizationConfiguration, private val viewModel: CardTokenizationViewModel, - private val lifecycleOwner: LifecycleOwner, private val cardScannerLauncher: POCardScannerLauncher, private val delegate: POCardTokenizationDelegate, private val callback: (ProcessOutResult) -> Unit, private val eventDispatcher: POEventDispatcher ) { - private val scope = lifecycleOwner.lifecycleScope - companion object { /** * Creates the card tokenization view component from a Fragment. @@ -86,7 +84,6 @@ class POCardTokenizationViewComponent private constructor( ), configuration = configuration, viewModel = viewModel, - lifecycleOwner = from, cardScannerLauncher = POCardScannerLauncher.create( from = from, callback = { result -> @@ -96,7 +93,11 @@ class POCardTokenizationViewComponent private constructor( delegate = delegate, callback = callback, eventDispatcher = viewModel.eventDispatcher - ) + ).apply { + from.viewLifecycleOwnerLiveData.observe(from) { lifecycleOwner -> + init(lifecycleOwner) + } + } } /** @@ -125,7 +126,6 @@ class POCardTokenizationViewComponent private constructor( ), configuration = configuration, viewModel = viewModel, - lifecycleOwner = from, cardScannerLauncher = POCardScannerLauncher.create( from = from, callback = { result -> @@ -135,7 +135,9 @@ class POCardTokenizationViewComponent private constructor( delegate = delegate, callback = callback, eventDispatcher = viewModel.eventDispatcher - ) + ).apply { + init(lifecycleOwner = from) + } } private fun POCardTokenizationViewComponentConfiguration.map() = @@ -178,35 +180,36 @@ class POCardTokenizationViewComponent private constructor( } } - init { - dispatchEvents() - dispatchState() - dispatchTokenizedCard() - dispatchEligibility() - dispatchPreferredScheme() - dispatchShouldContinue() - collectSideEffects() - collectCompletion() + private fun init(lifecycleOwner: LifecycleOwner) { + val coroutineScope = lifecycleOwner.lifecycleScope + dispatchEvents(coroutineScope) + dispatchState(coroutineScope) + dispatchTokenizedCard(coroutineScope) + dispatchEligibility(coroutineScope) + dispatchPreferredScheme(coroutineScope) + dispatchShouldContinue(coroutineScope) + collectSideEffects(lifecycleOwner) + collectCompletion(lifecycleOwner) viewModel.start() } - private fun dispatchEvents() { + private fun dispatchEvents(coroutineScope: CoroutineScope) { eventDispatcher.subscribe( - coroutineScope = scope + coroutineScope ) { delegate.onEvent(it) } } - private fun dispatchState() { + private fun dispatchState(coroutineScope: CoroutineScope) { eventDispatcher.subscribe( - coroutineScope = scope + coroutineScope ) { delegate.onStateChanged(it) } } - private fun dispatchTokenizedCard() { + private fun dispatchTokenizedCard(coroutineScope: CoroutineScope) { eventDispatcher.subscribeForRequest( - coroutineScope = scope + coroutineScope ) { request -> - scope.launch { + coroutineScope.launch { val result = delegate.processTokenizedCard( card = request.card, saveCard = request.saveCard @@ -216,11 +219,11 @@ class POCardTokenizationViewComponent private constructor( } } - private fun dispatchEligibility() { + private fun dispatchEligibility(coroutineScope: CoroutineScope) { eventDispatcher.subscribeForRequest( - coroutineScope = scope + coroutineScope ) { request -> - scope.launch { + coroutineScope.launch { val eligibility = delegate.evaluateEligibility( iin = request.iin, issuerInformation = request.issuerInformation @@ -230,30 +233,30 @@ class POCardTokenizationViewComponent private constructor( } } - private fun dispatchPreferredScheme() { + private fun dispatchPreferredScheme(coroutineScope: CoroutineScope) { eventDispatcher.subscribeForRequest( - coroutineScope = scope + coroutineScope ) { request -> - scope.launch { + coroutineScope.launch { val preferredScheme = delegate.preferredScheme(request.issuerInformation) eventDispatcher.send(request.toResponse(preferredScheme)) } } } - private fun dispatchShouldContinue() { + private fun dispatchShouldContinue(coroutineScope: CoroutineScope) { eventDispatcher.subscribeForRequest( - coroutineScope = scope + coroutineScope ) { request -> - scope.launch { + coroutineScope.launch { val shouldContinue = delegate.shouldContinue(request.failure) eventDispatcher.send(request.toResponse(shouldContinue)) } } } - private fun collectSideEffects() { - scope.launch { + private fun collectSideEffects(lifecycleOwner: LifecycleOwner) { + lifecycleOwner.lifecycleScope.launch { lifecycleOwner.repeatOnLifecycle(state = Lifecycle.State.STARTED) { withContext(Dispatchers.Main.immediate) { viewModel.sideEffects.collect { sideEffect -> @@ -268,8 +271,8 @@ class POCardTokenizationViewComponent private constructor( } } - private fun collectCompletion() { - scope.launch { + private fun collectCompletion(lifecycleOwner: LifecycleOwner) { + lifecycleOwner.lifecycleScope.launch { lifecycleOwner.repeatOnLifecycle(state = Lifecycle.State.STARTED) { viewModel.completion.collect { completion -> when (completion) { From 025cfdedc2e65f5c5de4434c90768da352bd3ca1 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Thu, 22 Oct 2026 13:51:14 +0300 Subject: [PATCH 6/6] AGP 9.4.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3e563df32..de0b7774a 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension buildscript { ext { - androidGradlePluginVersion = '9.3.1' + androidGradlePluginVersion = '9.4.1' kotlinVersion = '2.1.20' kspVersion = '2.1.20-1.0.32' dokkaVersion = '1.9.20'