From b97ad294f3b63ea1e5fef333a5b49f7b10a7e4eb Mon Sep 17 00:00:00 2001 From: "Carlos M." Date: Mon, 25 Mar 2024 09:54:01 -0700 Subject: [PATCH 1/7] Migrates more screens out of mavs. --- financial-connections/detekt-baseline.xml | 1 + .../core/FinancialConnectionsViewModel.kt | 17 +++--- .../FinancialConnectionsSheetNativeModule.kt | 12 +---- .../AccountPickerPreviewParameterProvider.kt | 6 +-- .../accountpicker/AccountPickerScreen.kt | 18 +++---- .../accountpicker/AccountPickerViewModel.kt | 54 +++++++++---------- .../features/consent/ConsentScreen.kt | 3 +- ...stitutionPickerPreviewParameterProvider.kt | 6 +-- .../InstitutionPickerViewModel.kt | 2 +- .../ManualEntrySuccessScreen.kt | 8 +-- .../ManualEntrySuccessViewModel.kt | 46 ++++++++-------- .../features/success/SuccessContent.kt | 4 +- .../SuccessPreviewParameterProvider.kt | 4 +- .../features/success/SuccessScreen.kt | 8 +-- .../features/success/SuccessViewModel.kt | 45 ++++++++-------- .../FinancialConnectionsErrorRepository.kt | 27 +++++----- .../repository/SuccessContentRepository.kt | 24 +++------ .../AccountPickerViewModelTest.kt | 11 ++-- 18 files changed, 135 insertions(+), 161 deletions(-) diff --git a/financial-connections/detekt-baseline.xml b/financial-connections/detekt-baseline.xml index 099fdef0d71..1257a434854 100644 --- a/financial-connections/detekt-baseline.xml +++ b/financial-connections/detekt-baseline.xml @@ -30,6 +30,7 @@ MaximumLineLength:com.stripe.android.financialconnections.presentation.FinancialConnectionsUrls.kt:41 MaximumLineLength:com.stripe.android.financialconnections.presentation.FinancialConnectionsUrls.kt:46 MaximumLineLength:com.stripe.android.financialconnections.presentation.FinancialConnectionsUrls.kt:9 + NestedBlockDepth:AccountPickerViewModel.kt$AccountPickerViewModel$fun onAccountClicked(account: PartnerAccount) NestedBlockDepth:InstitutionPickerScreen.kt$private fun LazyListScope.searchResults( isInputEmpty: Boolean, payload: Payload, selectedInstitutionId: String?, onInstitutionSelected: (FinancialConnectionsInstitution, Boolean) -> Unit, institutions: Async<InstitutionResponse>, onManualEntryClick: () -> Unit, onSearchMoreClick: () -> Unit ) SwallowedException:PollAttachPaymentAccount.kt$PollAttachPaymentAccount$e: StripeException SwallowedException:PollAuthorizationSessionAccounts.kt$PollAuthorizationSessionAccounts$e: StripeException diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/core/FinancialConnectionsViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/core/FinancialConnectionsViewModel.kt index 604fcfa4912..b0f48f82d32 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/core/FinancialConnectionsViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/core/FinancialConnectionsViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope import com.stripe.android.financialconnections.core.Async.Fail import com.stripe.android.financialconnections.core.Async.Loading import com.stripe.android.financialconnections.core.Async.Success +import com.stripe.android.financialconnections.core.Async.Uninitialized import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -23,22 +24,18 @@ internal abstract class FinancialConnectionsViewModel( val stateFlow: StateFlow = _stateFlow.asStateFlow() protected open fun (suspend () -> T).execute( - onSuccess: (T) -> Unit = {}, - onFail: (Throwable) -> Unit = {}, reducer: S.(Async) -> S ): Job { return viewModelScope.launch { - setState { reducer(Loading) } + setState { reducer(Loading()) } val result = runCatching { this@execute() } // update state. result.fold( onSuccess = { data -> setState { reducer(Success(data)) } - onSuccess(data) }, onFailure = { throwable -> setState { reducer(Fail(throwable)) } - onFail(throwable) } ) } @@ -46,8 +43,8 @@ internal abstract class FinancialConnectionsViewModel( protected open fun onAsync( prop: KProperty1>, - onSuccess: (T) -> Unit = {}, - onFail: (Throwable) -> Unit = {} + onSuccess: suspend (T) -> Unit = {}, + onFail: suspend (Throwable) -> Unit = {} ) { viewModelScope.launch { stateFlow.map { prop.get(it) } @@ -56,8 +53,8 @@ internal abstract class FinancialConnectionsViewModel( when (async) { is Success -> onSuccess(async()) is Fail -> onFail(async.error) - Loading -> Unit - Async.Uninitialized -> Unit + is Loading -> Unit + Uninitialized -> Unit } } } @@ -70,7 +67,7 @@ internal sealed class Async( private val value: T? ) { data object Uninitialized : Async(value = null) - data object Loading : Async(value = null) + data class Loading(val value: T? = null) : Async(value = value) data class Success(private val value: T) : Async(value = value) { override operator fun invoke(): T = value } diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/di/FinancialConnectionsSheetNativeModule.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/di/FinancialConnectionsSheetNativeModule.kt index 296fff840cf..549abb755e0 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/di/FinancialConnectionsSheetNativeModule.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/di/FinancialConnectionsSheetNativeModule.kt @@ -153,19 +153,11 @@ internal interface FinancialConnectionsSheetNativeModule { @Singleton @Provides - fun providesSaveToLinkWithStripeSucceededRepository( - @IOContext workContext: CoroutineContext - ): SuccessContentRepository = SuccessContentRepositoryImpl( - CoroutineScope(SupervisorJob() + workContext) - ) + fun providesSaveToLinkWithStripeSucceededRepository(): SuccessContentRepository = SuccessContentRepositoryImpl() @Singleton @Provides - fun providesFinancialConnectionsErrorRepository( - @IOContext workContext: CoroutineContext - ) = FinancialConnectionsErrorRepository( - CoroutineScope(SupervisorJob() + workContext) - ) + fun providesFinancialConnectionsErrorRepository() = FinancialConnectionsErrorRepository() @Singleton @Provides diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerPreviewParameterProvider.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerPreviewParameterProvider.kt index 22422523b05..a953a90e9a9 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerPreviewParameterProvider.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerPreviewParameterProvider.kt @@ -1,10 +1,10 @@ package com.stripe.android.financialconnections.features.accountpicker import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success import com.stripe.android.core.exception.APIException +import com.stripe.android.financialconnections.core.Async.Fail +import com.stripe.android.financialconnections.core.Async.Loading +import com.stripe.android.financialconnections.core.Async.Success import com.stripe.android.financialconnections.exception.AccountNoneEligibleForPaymentMethodError import com.stripe.android.financialconnections.features.accountpicker.AccountPickerState.SelectionMode import com.stripe.android.financialconnections.features.common.MerchantDataAccessModel diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerScreen.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerScreen.kt index 52835a7a076..1b55de0d699 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerScreen.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerScreen.kt @@ -22,6 +22,7 @@ import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -31,14 +32,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized -import com.airbnb.mvrx.compose.collectAsState -import com.airbnb.mvrx.compose.mavericksViewModel import com.stripe.android.financialconnections.R +import com.stripe.android.financialconnections.core.Async +import com.stripe.android.financialconnections.core.Async.Fail +import com.stripe.android.financialconnections.core.Async.Loading +import com.stripe.android.financialconnections.core.Async.Success +import com.stripe.android.financialconnections.core.Async.Uninitialized +import com.stripe.android.financialconnections.core.paneViewModel import com.stripe.android.financialconnections.exception.AccountLoadError import com.stripe.android.financialconnections.exception.AccountNoneEligibleForPaymentMethodError import com.stripe.android.financialconnections.features.accountpicker.AccountPickerClickableText.DATA @@ -68,9 +68,9 @@ import kotlinx.coroutines.launch @Composable internal fun AccountPickerScreen() { - val viewModel: AccountPickerViewModel = mavericksViewModel() + val viewModel: AccountPickerViewModel = paneViewModel { AccountPickerViewModel.factory(it) } val parentViewModel = parentViewModel() - val state: State = viewModel.collectAsState() + val state: State = viewModel.stateFlow.collectAsState() BackHandler(true) {} val bottomSheetState = rememberModalBottomSheetState( diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerViewModel.kt index b54949260de..336fc505b2b 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerViewModel.kt @@ -1,12 +1,9 @@ package com.stripe.android.financialconnections.features.accountpicker -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MavericksViewModel -import com.airbnb.mvrx.MavericksViewModelFactory -import com.airbnb.mvrx.Uninitialized -import com.airbnb.mvrx.ViewModelContext +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory import com.stripe.android.core.Logger import com.stripe.android.financialconnections.FinancialConnections import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsEvent.AccountSelected @@ -19,6 +16,11 @@ import com.stripe.android.financialconnections.analytics.FinancialConnectionsAna import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsTracker import com.stripe.android.financialconnections.analytics.FinancialConnectionsEvent.Name import com.stripe.android.financialconnections.analytics.logError +import com.stripe.android.financialconnections.core.Async +import com.stripe.android.financialconnections.core.Async.Loading +import com.stripe.android.financialconnections.core.Async.Uninitialized +import com.stripe.android.financialconnections.core.FinancialConnectionsViewModel +import com.stripe.android.financialconnections.di.FinancialConnectionsSheetNativeComponent import com.stripe.android.financialconnections.domain.GetOrFetchSync import com.stripe.android.financialconnections.domain.PollAuthorizationSessionAccounts import com.stripe.android.financialconnections.domain.SelectAccounts @@ -35,7 +37,6 @@ import com.stripe.android.financialconnections.navigation.Destination.ManualEntr import com.stripe.android.financialconnections.navigation.Destination.Reset import com.stripe.android.financialconnections.navigation.NavigationManager import com.stripe.android.financialconnections.navigation.destination -import com.stripe.android.financialconnections.ui.FinancialConnectionsSheetNativeActivity import com.stripe.android.financialconnections.ui.HandleClickableUrl import com.stripe.android.financialconnections.utils.measureTimeMillis import kotlinx.coroutines.launch @@ -51,7 +52,7 @@ internal class AccountPickerViewModel @Inject constructor( private val handleClickableUrl: HandleClickableUrl, private val logger: Logger, private val pollAuthorizationSessionAccounts: PollAuthorizationSessionAccounts -) : MavericksViewModel(initialState) { +) : FinancialConnectionsViewModel(initialState) { init { logErrors() @@ -61,7 +62,7 @@ internal class AccountPickerViewModel @Inject constructor( private fun loadAccounts() { suspend { - val state = awaitState() + val state = stateFlow.value val sync = getOrFetchSync() val dataAccessNotice = sync.text?.consent?.dataAccessNotice val manifest = sync.manifest @@ -180,7 +181,7 @@ internal class AccountPickerViewModel @Inject constructor( ) } - fun onAccountClicked(account: PartnerAccount) = withState { state -> + fun onAccountClicked(account: PartnerAccount) = stateFlow.value.let { state -> state.payload()?.let { payload -> val selectedIds = state.selectedIds val newSelectedIds = when (payload.selectionMode) { @@ -236,7 +237,7 @@ internal class AccountPickerViewModel @Inject constructor( eventTracker.track(ClickLinkAccounts(PANE)) } FinancialConnections.emitEvent(name = Name.ACCOUNTS_SELECTED) - withState { state -> + stateFlow.value.let { state -> state.payload()?.let { submitAccounts( selectedIds = state.selectedIds, @@ -308,21 +309,18 @@ internal class AccountPickerViewModel @Inject constructor( setState { copy(viewEffect = null) } } - companion object : - MavericksViewModelFactory { - - override fun create( - viewModelContext: ViewModelContext, - state: AccountPickerState - ): AccountPickerViewModel { - return viewModelContext.activity() - .viewModel - .activityRetainedComponent - .accountPickerBuilder - .initialState(state) - .build() - .viewModel - } + companion object { + + fun factory(parentComponent: FinancialConnectionsSheetNativeComponent): ViewModelProvider.Factory = + viewModelFactory { + initializer { + parentComponent + .accountPickerBuilder + .initialState(AccountPickerState()) + .build() + .viewModel + } + } private val PANE = Pane.ACCOUNT_PICKER } @@ -334,7 +332,7 @@ internal data class AccountPickerState( val selectAccounts: Async = Uninitialized, val selectedIds: Set = emptySet(), val viewEffect: ViewEffect? = null -) : MavericksState { +) { val submitLoading: Boolean get() = payload is Loading || selectAccounts is Loading diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/consent/ConsentScreen.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/consent/ConsentScreen.kt index 0177617881c..a77ea1919bc 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/consent/ConsentScreen.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/consent/ConsentScreen.kt @@ -114,7 +114,8 @@ private fun ConsentContent( onCloseFromErrorClick: (Throwable) -> Unit ) { when (val result = state.consent) { - Uninitialized, Loading -> ConsentLoadingContent() + Uninitialized, + is Loading -> ConsentLoadingContent() is Success -> LoadedContent( payload = result(), diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/institutionpicker/InstitutionPickerPreviewParameterProvider.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/institutionpicker/InstitutionPickerPreviewParameterProvider.kt index 6dcddd6ba62..2e5c1f894f4 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/institutionpicker/InstitutionPickerPreviewParameterProvider.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/institutionpicker/InstitutionPickerPreviewParameterProvider.kt @@ -28,7 +28,7 @@ internal class InstitutionPickerPreviewParameterProvider : private fun initialLoading() = InstitutionPreviewState( state = InstitutionPickerState( previewText = null, - payload = Loading, + payload = Loading(), searchInstitutions = Uninitialized, ), initialScroll = 0 @@ -47,7 +47,7 @@ internal class InstitutionPickerPreviewParameterProvider : state = InstitutionPickerState( previewText = "Some query", payload = Success(payload()), - searchInstitutions = Loading, + searchInstitutions = Loading(), ), initialScroll = 0 ) @@ -122,7 +122,7 @@ internal class InstitutionPickerPreviewParameterProvider : payload = Success(payload()), searchInstitutions = Success(institutionResponse(FEW_INSTITUTIONS)), selectedInstitutionId = "2", - createSessionForInstitution = Loading, + createSessionForInstitution = Loading(), ), initialScroll = 0 ) diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/institutionpicker/InstitutionPickerViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/institutionpicker/InstitutionPickerViewModel.kt index ad61a1e4ae6..a1ff054c2e0 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/institutionpicker/InstitutionPickerViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/institutionpicker/InstitutionPickerViewModel.kt @@ -164,7 +164,7 @@ internal class InstitutionPickerViewModel @Inject constructor( ) } }.execute { - copy(searchInstitutions = if (it.isCancellationError()) Loading else it) + copy(searchInstitutions = if (it.isCancellationError()) Loading() else it) } } diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/manualentrysuccess/ManualEntrySuccessScreen.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/manualentrysuccess/ManualEntrySuccessScreen.kt index d6ef96adf34..bdaa5ba5de2 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/manualentrysuccess/ManualEntrySuccessScreen.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/manualentrysuccess/ManualEntrySuccessScreen.kt @@ -2,9 +2,9 @@ package com.stripe.android.financialconnections.features.manualentrysuccess import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import com.airbnb.mvrx.compose.collectAsState -import com.airbnb.mvrx.compose.mavericksViewModel +import com.stripe.android.financialconnections.core.paneViewModel import com.stripe.android.financialconnections.features.success.SuccessContent import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest import com.stripe.android.financialconnections.presentation.parentViewModel @@ -12,8 +12,8 @@ import com.stripe.android.financialconnections.presentation.parentViewModel @Composable internal fun ManualEntrySuccessScreen() { val parentViewModel = parentViewModel() - val viewModel: ManualEntrySuccessViewModel = mavericksViewModel() - val state by viewModel.collectAsState() + val viewModel: ManualEntrySuccessViewModel = paneViewModel { ManualEntrySuccessViewModel.factory(it) } + val state by viewModel.stateFlow.collectAsState() BackHandler(true) {} SuccessContent( completeSessionAsync = state.completeSession, diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/manualentrysuccess/ManualEntrySuccessViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/manualentrysuccess/ManualEntrySuccessViewModel.kt index 030ba06d0d7..5035cf60621 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/manualentrysuccess/ManualEntrySuccessViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/manualentrysuccess/ManualEntrySuccessViewModel.kt @@ -1,22 +1,23 @@ package com.stripe.android.financialconnections.features.manualentrysuccess -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MavericksViewModel -import com.airbnb.mvrx.MavericksViewModelFactory -import com.airbnb.mvrx.Uninitialized -import com.airbnb.mvrx.ViewModelContext +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsEvent.ClickDone import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsEvent.PaneLoaded import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsTracker +import com.stripe.android.financialconnections.core.Async +import com.stripe.android.financialconnections.core.Async.Loading +import com.stripe.android.financialconnections.core.Async.Uninitialized +import com.stripe.android.financialconnections.core.FinancialConnectionsViewModel +import com.stripe.android.financialconnections.di.FinancialConnectionsSheetNativeComponent import com.stripe.android.financialconnections.domain.GetManifest import com.stripe.android.financialconnections.domain.NativeAuthFlowCoordinator import com.stripe.android.financialconnections.features.success.SuccessState import com.stripe.android.financialconnections.model.FinancialConnectionsSession import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane import com.stripe.android.financialconnections.repository.SuccessContentRepository -import com.stripe.android.financialconnections.ui.FinancialConnectionsSheetNativeActivity import kotlinx.coroutines.launch import javax.inject.Inject @@ -26,7 +27,7 @@ internal class ManualEntrySuccessViewModel @Inject constructor( private val successContentRepository: SuccessContentRepository, private val eventTracker: FinancialConnectionsAnalyticsTracker, private val nativeAuthFlowCoordinator: NativeAuthFlowCoordinator, -) : MavericksViewModel(initialState) { +) : FinancialConnectionsViewModel(initialState) { init { suspend { @@ -50,25 +51,22 @@ internal class ManualEntrySuccessViewModel @Inject constructor( } } - companion object : - MavericksViewModelFactory { + companion object { - override fun create( - viewModelContext: ViewModelContext, - state: ManualEntrySuccessState - ): ManualEntrySuccessViewModel { - return viewModelContext.activity() - .viewModel - .activityRetainedComponent - .manualEntrySuccessBuilder - .initialState(state) - .build() - .viewModel - } + fun factory(parentComponent: FinancialConnectionsSheetNativeComponent): ViewModelProvider.Factory = + viewModelFactory { + initializer { + parentComponent + .manualEntrySuccessBuilder + .initialState(ManualEntrySuccessState()) + .build() + .viewModel + } + } } } internal data class ManualEntrySuccessState( val payload: Async = Uninitialized, val completeSession: Async = Uninitialized -) : MavericksState +) diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/success/SuccessContent.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/success/SuccessContent.kt index 63740c3632a..dde88ba260a 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/success/SuccessContent.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/success/SuccessContent.kt @@ -41,9 +41,9 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Loading import com.stripe.android.financialconnections.R +import com.stripe.android.financialconnections.core.Async +import com.stripe.android.financialconnections.core.Async.Loading import com.stripe.android.financialconnections.features.common.LoadingSpinner import com.stripe.android.financialconnections.features.success.SuccessState.Payload import com.stripe.android.financialconnections.model.FinancialConnectionsSession diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/success/SuccessPreviewParameterProvider.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/success/SuccessPreviewParameterProvider.kt index 3a8d7486303..3a76e491b18 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/success/SuccessPreviewParameterProvider.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/success/SuccessPreviewParameterProvider.kt @@ -1,8 +1,8 @@ package com.stripe.android.financialconnections.features.success import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized +import com.stripe.android.financialconnections.core.Async.Success +import com.stripe.android.financialconnections.core.Async.Uninitialized import com.stripe.android.financialconnections.ui.TextResource internal class SuccessPreviewParameterProvider : diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/success/SuccessScreen.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/success/SuccessScreen.kt index babfad2aa79..d51b1964206 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/success/SuccessScreen.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/success/SuccessScreen.kt @@ -3,16 +3,16 @@ package com.stripe.android.financialconnections.features.success import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.State -import com.airbnb.mvrx.compose.collectAsState -import com.airbnb.mvrx.compose.mavericksViewModel +import androidx.compose.runtime.collectAsState +import com.stripe.android.financialconnections.core.paneViewModel import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane import com.stripe.android.financialconnections.presentation.parentViewModel @Composable internal fun SuccessScreen() { - val viewModel: SuccessViewModel = mavericksViewModel() + val viewModel: SuccessViewModel = paneViewModel { SuccessViewModel.factory(it) } val parentViewModel = parentViewModel() - val state: State = viewModel.collectAsState() + val state: State = viewModel.stateFlow.collectAsState() BackHandler(enabled = true) {} SuccessContent( completeSessionAsync = state.value.completeSession, diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/success/SuccessViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/success/SuccessViewModel.kt index f7197c784ad..d38ba45a0a7 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/success/SuccessViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/success/SuccessViewModel.kt @@ -1,16 +1,18 @@ package com.stripe.android.financialconnections.features.success -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MavericksViewModel -import com.airbnb.mvrx.MavericksViewModelFactory -import com.airbnb.mvrx.Uninitialized -import com.airbnb.mvrx.ViewModelContext +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory import com.stripe.android.core.Logger import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsEvent.ClickDone import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsEvent.PaneLoaded import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsTracker +import com.stripe.android.financialconnections.core.Async +import com.stripe.android.financialconnections.core.Async.Loading +import com.stripe.android.financialconnections.core.Async.Uninitialized +import com.stripe.android.financialconnections.core.FinancialConnectionsViewModel +import com.stripe.android.financialconnections.di.FinancialConnectionsSheetNativeComponent import com.stripe.android.financialconnections.domain.GetCachedAccounts import com.stripe.android.financialconnections.domain.GetManifest import com.stripe.android.financialconnections.domain.NativeAuthFlowCoordinator @@ -19,7 +21,6 @@ import com.stripe.android.financialconnections.features.common.useContinueWithMe import com.stripe.android.financialconnections.model.FinancialConnectionsSession import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane import com.stripe.android.financialconnections.repository.SuccessContentRepository -import com.stripe.android.financialconnections.ui.FinancialConnectionsSheetNativeActivity import com.stripe.android.financialconnections.ui.TextResource import kotlinx.coroutines.launch import javax.inject.Inject @@ -32,7 +33,7 @@ internal class SuccessViewModel @Inject constructor( private val eventTracker: FinancialConnectionsAnalyticsTracker, private val logger: Logger, private val nativeAuthFlowCoordinator: NativeAuthFlowCoordinator -) : MavericksViewModel(initialState) { +) : FinancialConnectionsViewModel(initialState) { init { observeAsyncs() @@ -78,20 +79,18 @@ internal class SuccessViewModel @Inject constructor( nativeAuthFlowCoordinator().emit(Complete()) } - companion object : MavericksViewModelFactory { + companion object { - override fun create( - viewModelContext: ViewModelContext, - state: SuccessState - ): SuccessViewModel { - return viewModelContext.activity() - .viewModel - .activityRetainedComponent - .successSubcomponent - .initialState(state) - .build() - .viewModel - } + fun factory(parentComponent: FinancialConnectionsSheetNativeComponent): ViewModelProvider.Factory = + viewModelFactory { + initializer { + parentComponent + .successSubcomponent + .initialState(SuccessState()) + .build() + .viewModel + } + } private val PANE = Pane.SUCCESS } @@ -100,7 +99,7 @@ internal class SuccessViewModel @Inject constructor( internal data class SuccessState( val payload: Async = Uninitialized, val completeSession: Async = Uninitialized -) : MavericksState { +) { data class Payload( val businessName: String?, diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsErrorRepository.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsErrorRepository.kt index 5857fcd2311..d4c1226fed2 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsErrorRepository.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsErrorRepository.kt @@ -1,30 +1,27 @@ package com.stripe.android.financialconnections.repository -import com.airbnb.mvrx.ExperimentalMavericksApi -import com.airbnb.mvrx.MavericksRepository import com.airbnb.mvrx.MavericksState -import com.stripe.android.financialconnections.BuildConfig -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update -@OptIn(ExperimentalMavericksApi::class) -internal class FinancialConnectionsErrorRepository( - coroutineScope: CoroutineScope -) : MavericksRepository( - initialState = State(), - coroutineScope = coroutineScope, - performCorrectnessValidations = BuildConfig.DEBUG, -) { +internal class FinancialConnectionsErrorRepository { - suspend fun get() = awaitState().error + private val state = MutableStateFlow(State()) + + suspend fun get() = state.value.error + + fun update(reducer: State.() -> State) { + state.update { reducer(it) } + } fun set(error: Throwable) { - setState { + update { copy(error = error) } } fun clear() { - setState { + update { copy(error = null) } } diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/SuccessContentRepository.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/SuccessContentRepository.kt index be7eb7cd959..be8abd6cfe7 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/SuccessContentRepository.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/SuccessContentRepository.kt @@ -1,12 +1,9 @@ package com.stripe.android.financialconnections.repository -import com.airbnb.mvrx.ExperimentalMavericksApi -import com.airbnb.mvrx.MavericksRepository -import com.airbnb.mvrx.MavericksState -import com.stripe.android.financialconnections.BuildConfig import com.stripe.android.financialconnections.repository.SuccessContentRepository.State import com.stripe.android.financialconnections.ui.TextResource -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update import javax.inject.Inject internal interface SuccessContentRepository { @@ -15,21 +12,16 @@ internal interface SuccessContentRepository { data class State( val customSuccessMessage: TextResource? = null - ) : MavericksState + ) } -@OptIn(ExperimentalMavericksApi::class) -internal class SuccessContentRepositoryImpl @Inject constructor( - coroutineScope: CoroutineScope -) : SuccessContentRepository, MavericksRepository( - initialState = State(), - coroutineScope = coroutineScope, - performCorrectnessValidations = BuildConfig.DEBUG, -) { +internal class SuccessContentRepositoryImpl @Inject constructor() : SuccessContentRepository { - override suspend fun get() = awaitState() + private val state = MutableStateFlow(State()) + + override suspend fun get() = state.value override fun update(reducer: State.() -> State) { - setState(reducer) + state.update { reducer(it) } } } diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerViewModelTest.kt index 1577b4cd713..342e4d00987 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerViewModelTest.kt @@ -1,7 +1,6 @@ package com.stripe.android.financialconnections.features.accountpicker import com.airbnb.mvrx.test.MavericksTestRule -import com.airbnb.mvrx.withState import com.google.common.truth.Truth.assertThat import com.stripe.android.core.Logger import com.stripe.android.financialconnections.ApiKeyFixtures.authorizationSession @@ -68,7 +67,7 @@ internal class AccountPickerViewModelTest { val viewModel = buildViewModel(AccountPickerState()) - withState(viewModel) { state -> + viewModel.stateFlow.value.let { state -> assertEquals(state.payload()!!.skipAccountSelection, true) } } @@ -91,7 +90,7 @@ internal class AccountPickerViewModelTest { val viewModel = buildViewModel(AccountPickerState()) - withState(viewModel) { state -> + viewModel.stateFlow.value.let { state -> val payload = state.payload()!! assertEquals(payload.skipAccountSelection, true) assertEquals(payload.shouldSkipPane, true) @@ -118,7 +117,7 @@ internal class AccountPickerViewModelTest { val viewModel = buildViewModel(AccountPickerState()) - withState(viewModel) { state -> + viewModel.stateFlow.value.let { state -> assertEquals(state.payload()!!.userSelectedSingleAccountInInstitution, true) assertEquals(state.payload()!!.shouldSkipPane, true) } @@ -144,7 +143,7 @@ internal class AccountPickerViewModelTest { val viewModel = buildViewModel(AccountPickerState()) - withState(viewModel) { state -> + viewModel.stateFlow.value.let { state -> assertThat(state.selectedIds).isEqualTo(setOf("selectable")) } @@ -178,7 +177,7 @@ internal class AccountPickerViewModelTest { val viewModel = buildViewModel(AccountPickerState()) - withState(viewModel) { state -> + viewModel.stateFlow.value.let { state -> assertThat(state.selectedIds).isEqualTo(setOf("selectable_1", "selectable_2")) } From ef76f4a6b3b6252bbd397ff841352886d736fe09 Mon Sep 17 00:00:00 2001 From: "Carlos M." Date: Mon, 25 Mar 2024 11:16:27 -0700 Subject: [PATCH 2/7] Migrates partner auth. --- .../core/FinancialConnectionsViewModel.kt | 5 +- .../bankauthrepair/BankAuthRepairScreen.kt | 8 +- .../bankauthrepair/BankAuthRepairViewModel.kt | 39 +++++----- .../features/common/SharedPartnerAuth.kt | 10 +-- .../PartnerAuthPreviewParameterProvider.kt | 6 +- .../features/partnerauth/PartnerAuthScreen.kt | 8 +- .../partnerauth/PartnerAuthViewModel.kt | 74 ++++++++++++------- .../partnerauth/SharedPartnerAuthState.kt | 41 ++++++---- 8 files changed, 109 insertions(+), 82 deletions(-) diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/core/FinancialConnectionsViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/core/FinancialConnectionsViewModel.kt index b0f48f82d32..a9420f5348d 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/core/FinancialConnectionsViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/core/FinancialConnectionsViewModel.kt @@ -24,10 +24,11 @@ internal abstract class FinancialConnectionsViewModel( val stateFlow: StateFlow = _stateFlow.asStateFlow() protected open fun (suspend () -> T).execute( - reducer: S.(Async) -> S + retainValue: KProperty1>? = null, + reducer: S.(Async) -> S, ): Job { return viewModelScope.launch { - setState { reducer(Loading()) } + setState { reducer(Loading(value = retainValue?.get(this)?.invoke())) } val result = runCatching { this@execute() } // update state. result.fold( diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/bankauthrepair/BankAuthRepairScreen.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/bankauthrepair/BankAuthRepairScreen.kt index f469803e8dd..d30fdbdeb85 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/bankauthrepair/BankAuthRepairScreen.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/bankauthrepair/BankAuthRepairScreen.kt @@ -2,16 +2,16 @@ package com.stripe.android.financialconnections.features.bankauthrepair import androidx.compose.runtime.Composable import androidx.compose.runtime.State -import com.airbnb.mvrx.compose.collectAsState -import com.airbnb.mvrx.compose.mavericksViewModel +import androidx.compose.runtime.collectAsState +import com.stripe.android.financialconnections.core.paneViewModel import com.stripe.android.financialconnections.features.common.SharedPartnerAuth import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthState @Composable internal fun BankAuthRepairScreen() { // step view model - val viewModel: BankAuthRepairViewModel = mavericksViewModel() - val state: State = viewModel.collectAsState() + val viewModel: BankAuthRepairViewModel = paneViewModel { BankAuthRepairViewModel.factory(it) } + val state: State = viewModel.stateFlow.collectAsState() SharedPartnerAuth( state = state.value, diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/bankauthrepair/BankAuthRepairViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/bankauthrepair/BankAuthRepairViewModel.kt index ba8fc3c480a..0f2b63a1d73 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/bankauthrepair/BankAuthRepairViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/bankauthrepair/BankAuthRepairViewModel.kt @@ -1,35 +1,30 @@ package com.stripe.android.financialconnections.features.bankauthrepair -import com.airbnb.mvrx.MavericksViewModel -import com.airbnb.mvrx.MavericksViewModelFactory -import com.airbnb.mvrx.ViewModelContext +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import com.stripe.android.financialconnections.core.FinancialConnectionsViewModel +import com.stripe.android.financialconnections.di.FinancialConnectionsSheetNativeComponent import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthState import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane -import com.stripe.android.financialconnections.ui.FinancialConnectionsSheetNativeActivity import javax.inject.Inject internal class BankAuthRepairViewModel @Inject constructor( initialState: SharedPartnerAuthState -) : MavericksViewModel(initialState) { +) : FinancialConnectionsViewModel(initialState) { - internal companion object : - MavericksViewModelFactory { + internal companion object { - override fun initialState(viewModelContext: ViewModelContext) = - SharedPartnerAuthState(pane = PANE) - - override fun create( - viewModelContext: ViewModelContext, - state: SharedPartnerAuthState - ): BankAuthRepairViewModel { - return viewModelContext.activity() - .viewModel - .activityRetainedComponent - .bankAuthRepairSubcomponent - .initialState(state) - .build() - .viewModel - } + fun factory(parentComponent: FinancialConnectionsSheetNativeComponent): ViewModelProvider.Factory = + viewModelFactory { + initializer { + parentComponent + .bankAuthRepairSubcomponent + .initialState(SharedPartnerAuthState(pane = PANE, savedState = null)) + .build() + .viewModel + } + } val PANE = Pane.BANK_AUTH_REPAIR } diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/common/SharedPartnerAuth.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/common/SharedPartnerAuth.kt index f4515fd247f..26e81ebf434 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/common/SharedPartnerAuth.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/common/SharedPartnerAuth.kt @@ -41,12 +41,12 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import com.stripe.android.financialconnections.R +import com.stripe.android.financialconnections.core.Async +import com.stripe.android.financialconnections.core.Async.Fail +import com.stripe.android.financialconnections.core.Async.Loading +import com.stripe.android.financialconnections.core.Async.Success +import com.stripe.android.financialconnections.core.Async.Uninitialized import com.stripe.android.financialconnections.core.collectAsState import com.stripe.android.financialconnections.features.partnerauth.PartnerAuthPreviewParameterProvider import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthState diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthPreviewParameterProvider.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthPreviewParameterProvider.kt index 052016136bc..ebd589f9a1e 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthPreviewParameterProvider.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthPreviewParameterProvider.kt @@ -1,9 +1,9 @@ package com.stripe.android.financialconnections.features.partnerauth import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized +import com.stripe.android.financialconnections.core.Async.Loading +import com.stripe.android.financialconnections.core.Async.Success +import com.stripe.android.financialconnections.core.Async.Uninitialized import com.stripe.android.financialconnections.model.Body import com.stripe.android.financialconnections.model.Cta import com.stripe.android.financialconnections.model.Display diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthScreen.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthScreen.kt index a5b11b0a818..23406afe5d3 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthScreen.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthScreen.kt @@ -2,14 +2,14 @@ package com.stripe.android.financialconnections.features.partnerauth import androidx.compose.runtime.Composable import androidx.compose.runtime.State -import com.airbnb.mvrx.compose.collectAsState -import com.airbnb.mvrx.compose.mavericksViewModel +import androidx.compose.runtime.collectAsState +import com.stripe.android.financialconnections.core.paneViewModel import com.stripe.android.financialconnections.features.common.SharedPartnerAuth @Composable internal fun PartnerAuthScreen(inModal: Boolean) { - val viewModel: PartnerAuthViewModel = mavericksViewModel() - val state: State = viewModel.collectAsState() + val viewModel: PartnerAuthViewModel = paneViewModel { PartnerAuthViewModel.factory(it) } + val state: State = viewModel.stateFlow.collectAsState() SharedPartnerAuth( inModal = inModal, diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModel.kt index 7820ff45f37..117107523b6 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModel.kt @@ -1,13 +1,14 @@ package com.stripe.android.financialconnections.features.partnerauth +import android.os.Bundle import android.webkit.URLUtil import androidx.core.net.toUri -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MavericksViewModel -import com.airbnb.mvrx.MavericksViewModelFactory -import com.airbnb.mvrx.Uninitialized -import com.airbnb.mvrx.ViewModelContext +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.createSavedStateHandle +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory import com.stripe.android.core.Logger import com.stripe.android.financialconnections.FinancialConnections import com.stripe.android.financialconnections.analytics.AuthSessionEvent @@ -23,7 +24,12 @@ import com.stripe.android.financialconnections.analytics.FinancialConnectionsAna import com.stripe.android.financialconnections.analytics.FinancialConnectionsEvent.Name import com.stripe.android.financialconnections.analytics.logError import com.stripe.android.financialconnections.browser.BrowserManager +import com.stripe.android.financialconnections.core.Async.Fail +import com.stripe.android.financialconnections.core.Async.Loading +import com.stripe.android.financialconnections.core.Async.Uninitialized +import com.stripe.android.financialconnections.core.FinancialConnectionsViewModel import com.stripe.android.financialconnections.di.APPLICATION_ID +import com.stripe.android.financialconnections.di.FinancialConnectionsSheetNativeComponent import com.stripe.android.financialconnections.domain.CancelAuthorizationSession import com.stripe.android.financialconnections.domain.CompleteAuthorizationSession import com.stripe.android.financialconnections.domain.GetOrFetchSync @@ -37,6 +43,8 @@ import com.stripe.android.financialconnections.exception.PartnerAuthError import com.stripe.android.financialconnections.exception.WebAuthFlowFailedException import com.stripe.android.financialconnections.features.common.enableRetrieveAuthSession import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthState.AuthenticationStatus.Action +import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthState.Companion.KEY_ACTIVE_AUTH_SESSION +import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthState.Companion.KEY_SAVED_STATE import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthState.Payload import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthState.ViewEffect import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthState.ViewEffect.OpenPartnerAuth @@ -49,7 +57,6 @@ import com.stripe.android.financialconnections.navigation.NavigationManager import com.stripe.android.financialconnections.navigation.PopUpToBehavior import com.stripe.android.financialconnections.navigation.destination import com.stripe.android.financialconnections.presentation.WebAuthFlowState -import com.stripe.android.financialconnections.ui.FinancialConnectionsSheetNativeActivity import com.stripe.android.financialconnections.utils.UriUtils import kotlinx.coroutines.launch import java.util.Date @@ -72,10 +79,12 @@ internal class PartnerAuthViewModel @Inject constructor( private val navigationManager: NavigationManager, private val pollAuthorizationSessionOAuthResults: PollAuthorizationSessionOAuthResults, private val logger: Logger, + savedStateHandle: SavedStateHandle, initialState: SharedPartnerAuthState -) : MavericksViewModel(initialState) { +) : FinancialConnectionsViewModel(initialState) { init { + savedStateHandle.registerSavedStateProvider() handleErrors() launchBrowserIfNonOauth() restoreOrCreateAuthSession() @@ -97,7 +106,12 @@ internal class PartnerAuthViewModel @Inject constructor( institution = requireNotNull(manifest.activeInstitution), authSession = authSession, ) - }.execute { copy(payload = it) } + }.execute { + copy( + payload = it, + activeAuthSession = it()?.authSession?.id + ) + } private fun recreateAuthSession() = suspend { val launchedEvent = Launched(Date()) @@ -130,9 +144,18 @@ internal class PartnerAuthViewModel @Inject constructor( ) } + private fun SavedStateHandle.registerSavedStateProvider() { + setSavedStateProvider(KEY_SAVED_STATE) { + val state = stateFlow.value + Bundle().apply { + putString(KEY_ACTIVE_AUTH_SESSION, state.activeAuthSession) + } + } + } + private fun launchBrowserIfNonOauth() { onAsync( - asyncProp = SharedPartnerAuthState::payload, + prop = SharedPartnerAuthState::payload, onSuccess = { // launch auth for non-OAuth (skip pre-pane). if (!it.authSession.isOAuth) { @@ -171,7 +194,7 @@ internal class PartnerAuthViewModel @Inject constructor( fun onLaunchAuthClick() { setState { copy(authenticationStatus = Loading(value = Status(Action.AUTHENTICATING))) } - withState { state -> + stateFlow.value.let { state -> val authSession = requireNotNull(state.payload()?.authSession) { "Payload shouldn't be null when the user launches the auth flow" } @@ -437,23 +460,20 @@ internal class PartnerAuthViewModel @Inject constructor( navigationManager.tryNavigateBack() } - companion object : MavericksViewModelFactory { - - override fun initialState(viewModelContext: ViewModelContext) = - SharedPartnerAuthState(pane = PANE) + companion object { - override fun create( - viewModelContext: ViewModelContext, - state: SharedPartnerAuthState - ): PartnerAuthViewModel { - return viewModelContext.activity() - .viewModel - .activityRetainedComponent - .partnerAuthSubcomponent - .initialState(state) - .build() - .viewModel - } + fun factory(parentComponent: FinancialConnectionsSheetNativeComponent): ViewModelProvider.Factory = + viewModelFactory { + initializer { + val savedStateHandle: SavedStateHandle = createSavedStateHandle() + val savedState = savedStateHandle.get(KEY_SAVED_STATE) + parentComponent + .partnerAuthSubcomponent + .initialState(SharedPartnerAuthState(pane = PANE, savedState = savedState)) + .build() + .viewModel + } + } private val PANE = Pane.PARTNER_AUTH } diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/SharedPartnerAuthState.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/SharedPartnerAuthState.kt index be7e55237d8..2c39e041138 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/SharedPartnerAuthState.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/SharedPartnerAuthState.kt @@ -1,28 +1,34 @@ package com.stripe.android.financialconnections.features.partnerauth -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.PersistState -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized +import android.os.Bundle +import com.stripe.android.financialconnections.core.Async +import com.stripe.android.financialconnections.core.Async.Fail +import com.stripe.android.financialconnections.core.Async.Loading +import com.stripe.android.financialconnections.core.Async.Success +import com.stripe.android.financialconnections.core.Async.Uninitialized import com.stripe.android.financialconnections.model.FinancialConnectionsAuthorizationSession import com.stripe.android.financialconnections.model.FinancialConnectionsInstitution -import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest +import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane internal data class SharedPartnerAuthState( /** * The active auth session id. Used across process kills to prevent re-creating the session * if one is already active. */ - @PersistState - val activeAuthSession: String? = null, - val pane: FinancialConnectionsSessionManifest.Pane, - val payload: Async = Uninitialized, - val viewEffect: ViewEffect? = null, - val authenticationStatus: Async = Uninitialized, -) : MavericksState { + val activeAuthSession: String?, + val pane: Pane, + val payload: Async, + val viewEffect: ViewEffect?, + val authenticationStatus: Async, +) { + + constructor(pane: Pane, savedState: Bundle?) : this( + activeAuthSession = savedState?.getString(KEY_ACTIVE_AUTH_SESSION), + pane = pane, + payload = Uninitialized, + viewEffect = null, + authenticationStatus = Uninitialized + ) data class Payload( val isStripeDirect: Boolean, @@ -65,4 +71,9 @@ internal data class SharedPartnerAuthState( internal enum class ClickableText(val value: String) { DATA("stripe://data-access-notice"), } + + companion object { + const val KEY_SAVED_STATE = "SharedPartnerAuthState" + const val KEY_ACTIVE_AUTH_SESSION = "activeAuthSession" + } } From dc361cfe0d227775b38e91aa9e786224f09e1d18 Mon Sep 17 00:00:00 2001 From: "Carlos M." Date: Mon, 25 Mar 2024 11:35:08 -0700 Subject: [PATCH 3/7] Removes active auth session field. --- .../bankauthrepair/BankAuthRepairViewModel.kt | 2 +- .../PartnerAuthPreviewParameterProvider.kt | 3 -- .../partnerauth/PartnerAuthViewModel.kt | 30 ++----------------- .../partnerauth/SharedPartnerAuthState.kt | 14 +-------- 4 files changed, 5 insertions(+), 44 deletions(-) diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/bankauthrepair/BankAuthRepairViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/bankauthrepair/BankAuthRepairViewModel.kt index 0f2b63a1d73..30482f3539a 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/bankauthrepair/BankAuthRepairViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/bankauthrepair/BankAuthRepairViewModel.kt @@ -20,7 +20,7 @@ internal class BankAuthRepairViewModel @Inject constructor( initializer { parentComponent .bankAuthRepairSubcomponent - .initialState(SharedPartnerAuthState(pane = PANE, savedState = null)) + .initialState(SharedPartnerAuthState(pane = PANE)) .build() .viewModel } diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthPreviewParameterProvider.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthPreviewParameterProvider.kt index ebd589f9a1e..064dc7e1bbc 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthPreviewParameterProvider.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthPreviewParameterProvider.kt @@ -46,7 +46,6 @@ internal class PartnerAuthPreviewParameterProvider : ), authenticationStatus = Uninitialized, viewEffect = null, - activeAuthSession = null, pane = Pane.PARTNER_AUTH ) @@ -54,7 +53,6 @@ internal class PartnerAuthPreviewParameterProvider : payload = Loading(), authenticationStatus = Uninitialized, viewEffect = null, - activeAuthSession = null, pane = Pane.PARTNER_AUTH ) @@ -78,7 +76,6 @@ internal class PartnerAuthPreviewParameterProvider : // While browser is showing, this Async is loading. authenticationStatus = Loading(), viewEffect = null, - activeAuthSession = null, pane = Pane.PARTNER_AUTH ) diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModel.kt index 117107523b6..f7b4222d619 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModel.kt @@ -1,11 +1,8 @@ package com.stripe.android.financialconnections.features.partnerauth -import android.os.Bundle import android.webkit.URLUtil import androidx.core.net.toUri -import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.createSavedStateHandle import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory @@ -43,8 +40,6 @@ import com.stripe.android.financialconnections.exception.PartnerAuthError import com.stripe.android.financialconnections.exception.WebAuthFlowFailedException import com.stripe.android.financialconnections.features.common.enableRetrieveAuthSession import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthState.AuthenticationStatus.Action -import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthState.Companion.KEY_ACTIVE_AUTH_SESSION -import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthState.Companion.KEY_SAVED_STATE import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthState.Payload import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthState.ViewEffect import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthState.ViewEffect.OpenPartnerAuth @@ -79,12 +74,10 @@ internal class PartnerAuthViewModel @Inject constructor( private val navigationManager: NavigationManager, private val pollAuthorizationSessionOAuthResults: PollAuthorizationSessionOAuthResults, private val logger: Logger, - savedStateHandle: SavedStateHandle, initialState: SharedPartnerAuthState ) : FinancialConnectionsViewModel(initialState) { init { - savedStateHandle.registerSavedStateProvider() handleErrors() launchBrowserIfNonOauth() restoreOrCreateAuthSession() @@ -107,10 +100,7 @@ internal class PartnerAuthViewModel @Inject constructor( authSession = authSession, ) }.execute { - copy( - payload = it, - activeAuthSession = it()?.authSession?.id - ) + copy(payload = it) } private fun recreateAuthSession() = suspend { @@ -138,19 +128,7 @@ internal class PartnerAuthViewModel @Inject constructor( // keeps existing payload to prevent showing full-screen loading. retainValue = SharedPartnerAuthState::payload ) { - copy( - payload = it, - activeAuthSession = it()?.authSession?.id - ) - } - - private fun SavedStateHandle.registerSavedStateProvider() { - setSavedStateProvider(KEY_SAVED_STATE) { - val state = stateFlow.value - Bundle().apply { - putString(KEY_ACTIVE_AUTH_SESSION, state.activeAuthSession) - } - } + copy(payload = it) } private fun launchBrowserIfNonOauth() { @@ -465,11 +443,9 @@ internal class PartnerAuthViewModel @Inject constructor( fun factory(parentComponent: FinancialConnectionsSheetNativeComponent): ViewModelProvider.Factory = viewModelFactory { initializer { - val savedStateHandle: SavedStateHandle = createSavedStateHandle() - val savedState = savedStateHandle.get(KEY_SAVED_STATE) parentComponent .partnerAuthSubcomponent - .initialState(SharedPartnerAuthState(pane = PANE, savedState = savedState)) + .initialState(SharedPartnerAuthState(pane = PANE)) .build() .viewModel } diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/SharedPartnerAuthState.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/SharedPartnerAuthState.kt index 2c39e041138..574c20b09db 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/SharedPartnerAuthState.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/SharedPartnerAuthState.kt @@ -1,6 +1,5 @@ package com.stripe.android.financialconnections.features.partnerauth -import android.os.Bundle import com.stripe.android.financialconnections.core.Async import com.stripe.android.financialconnections.core.Async.Fail import com.stripe.android.financialconnections.core.Async.Loading @@ -11,19 +10,13 @@ import com.stripe.android.financialconnections.model.FinancialConnectionsInstitu import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane internal data class SharedPartnerAuthState( - /** - * The active auth session id. Used across process kills to prevent re-creating the session - * if one is already active. - */ - val activeAuthSession: String?, val pane: Pane, val payload: Async, val viewEffect: ViewEffect?, val authenticationStatus: Async, ) { - constructor(pane: Pane, savedState: Bundle?) : this( - activeAuthSession = savedState?.getString(KEY_ACTIVE_AUTH_SESSION), + constructor(pane: Pane) : this( pane = pane, payload = Uninitialized, viewEffect = null, @@ -71,9 +64,4 @@ internal data class SharedPartnerAuthState( internal enum class ClickableText(val value: String) { DATA("stripe://data-access-notice"), } - - companion object { - const val KEY_SAVED_STATE = "SharedPartnerAuthState" - const val KEY_ACTIVE_AUTH_SESSION = "activeAuthSession" - } } From 641dd20d17d6136909d2a7695bab8ee0415b4d7d Mon Sep 17 00:00:00 2001 From: "Carlos M." Date: Mon, 25 Mar 2024 11:46:33 -0700 Subject: [PATCH 4/7] Updates tests. --- .../partnerauth/PartnerAuthViewModelTest.kt | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModelTest.kt index afe87cbf1d5..da5fad466d4 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModelTest.kt @@ -1,8 +1,6 @@ package com.stripe.android.financialconnections.features.partnerauth -import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.test.MavericksTestRule -import com.airbnb.mvrx.withState import com.google.common.truth.Truth.assertThat import com.stripe.android.core.Logger import com.stripe.android.core.exception.APIException @@ -73,16 +71,15 @@ internal class PartnerAuthViewModelTest { throw unplannedDowntimeError } - val viewModel = createViewModel() + createViewModel() + + handleError.assertError( + extraMessage = "Error fetching payload / posting AuthSession", + error = unplannedDowntimeError, + pane = Pane.PARTNER_AUTH, + displayErrorScreen = true + ) - withState(viewModel) { - handleError.assertError( - extraMessage = "Error fetching payload / posting AuthSession", - error = unplannedDowntimeError, - pane = Pane.PARTNER_AUTH, - displayErrorScreen = true - ) - } } @Test @@ -272,13 +269,7 @@ internal class PartnerAuthViewModelTest { } private fun createViewModel( - initialState: SharedPartnerAuthState = SharedPartnerAuthState( - activeAuthSession = null, - pane = Pane.PARTNER_AUTH, - payload = Uninitialized, - viewEffect = null, - authenticationStatus = Uninitialized - ) + initialState: SharedPartnerAuthState = SharedPartnerAuthState(Pane.PARTNER_AUTH) ): PartnerAuthViewModel { return PartnerAuthViewModel( navigationManager = TestNavigationManager(), From ef70ff1ae23fafaec00ba543b317ab7b8ddc1851 Mon Sep 17 00:00:00 2001 From: "Carlos M." Date: Mon, 25 Mar 2024 12:08:34 -0700 Subject: [PATCH 5/7] Updates attach payment viewmodel. --- .../attachpayment/AttachPaymentScreen.kt | 20 +++++---- .../attachpayment/AttachPaymentViewModel.kt | 42 +++++++++---------- .../partnerauth/PartnerAuthViewModelTest.kt | 1 - 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/attachpayment/AttachPaymentScreen.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/attachpayment/AttachPaymentScreen.kt index 21dbb3934ee..c6927f5a7de 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/attachpayment/AttachPaymentScreen.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/attachpayment/AttachPaymentScreen.kt @@ -2,14 +2,14 @@ package com.stripe.android.financialconnections.features.attachpayment import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.ui.tooling.preview.Preview -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized -import com.airbnb.mvrx.compose.collectAsState -import com.airbnb.mvrx.compose.mavericksViewModel +import com.stripe.android.financialconnections.core.Async +import com.stripe.android.financialconnections.core.Async.Fail +import com.stripe.android.financialconnections.core.Async.Loading +import com.stripe.android.financialconnections.core.Async.Success +import com.stripe.android.financialconnections.core.Async.Uninitialized +import com.stripe.android.financialconnections.core.paneViewModel import com.stripe.android.financialconnections.exception.AccountNumberRetrievalError import com.stripe.android.financialconnections.features.common.AccountNumberRetrievalErrorContent import com.stripe.android.financialconnections.features.common.FullScreenGenericLoading @@ -23,9 +23,9 @@ import com.stripe.android.financialconnections.ui.components.FinancialConnection @Composable internal fun AttachPaymentScreen() { - val viewModel: AttachPaymentViewModel = mavericksViewModel() + val viewModel: AttachPaymentViewModel = paneViewModel { AttachPaymentViewModel.factory(it) } val parentViewModel = parentViewModel() - val state = viewModel.collectAsState() + val state = viewModel.stateFlow.collectAsState() BackHandler(enabled = true) {} AttachPaymentContent( attachPayment = state.value.linkPaymentAccount, @@ -56,6 +56,7 @@ private fun AttachPaymentContent( is Loading, is Uninitialized, is Success -> FullScreenGenericLoading() + is Fail -> ErrorContent( error = attachPayment.error, onSelectAnotherBank = onSelectAnotherBank, @@ -79,6 +80,7 @@ private fun ErrorContent( onSelectAnotherBank = onSelectAnotherBank, onEnterDetailsManually = onEnterDetailsManually ) + else -> UnclassifiedErrorContent { onCloseFromErrorClick(error) } } } diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/attachpayment/AttachPaymentViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/attachpayment/AttachPaymentViewModel.kt index 31199a518b5..3733654cb01 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/attachpayment/AttachPaymentViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/attachpayment/AttachPaymentViewModel.kt @@ -1,16 +1,17 @@ package com.stripe.android.financialconnections.features.attachpayment -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MavericksState -import com.airbnb.mvrx.MavericksViewModel -import com.airbnb.mvrx.MavericksViewModelFactory -import com.airbnb.mvrx.Uninitialized -import com.airbnb.mvrx.ViewModelContext +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory import com.stripe.android.core.Logger import com.stripe.android.financialconnections.R import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsEvent.PollAttachPaymentsSucceeded import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsTracker import com.stripe.android.financialconnections.analytics.logError +import com.stripe.android.financialconnections.core.Async +import com.stripe.android.financialconnections.core.Async.Uninitialized +import com.stripe.android.financialconnections.core.FinancialConnectionsViewModel +import com.stripe.android.financialconnections.di.FinancialConnectionsSheetNativeComponent import com.stripe.android.financialconnections.domain.GetCachedAccounts import com.stripe.android.financialconnections.domain.GetCachedConsumerSession import com.stripe.android.financialconnections.domain.GetOrFetchSync @@ -23,7 +24,6 @@ import com.stripe.android.financialconnections.navigation.Destination.Reset import com.stripe.android.financialconnections.navigation.NavigationManager import com.stripe.android.financialconnections.navigation.destination import com.stripe.android.financialconnections.repository.SuccessContentRepository -import com.stripe.android.financialconnections.ui.FinancialConnectionsSheetNativeActivity import com.stripe.android.financialconnections.ui.TextResource.PluralId import com.stripe.android.financialconnections.utils.measureTimeMillis import javax.inject.Inject @@ -38,7 +38,7 @@ internal class AttachPaymentViewModel @Inject constructor( private val getOrFetchSync: GetOrFetchSync, private val getCachedConsumerSession: GetCachedConsumerSession, private val logger: Logger -) : MavericksViewModel(initialState) { +) : FinancialConnectionsViewModel(initialState) { init { logErrors() @@ -103,20 +103,18 @@ internal class AttachPaymentViewModel @Inject constructor( fun onSelectAnotherBank() = navigationManager.tryNavigateTo(Reset(referrer = PANE)) - companion object : MavericksViewModelFactory { + companion object { - override fun create( - viewModelContext: ViewModelContext, - state: AttachPaymentState - ): AttachPaymentViewModel { - return viewModelContext.activity() - .viewModel - .activityRetainedComponent - .attachPaymentSubcomponent - .initialState(state) - .build() - .viewModel - } + fun factory(parentComponent: FinancialConnectionsSheetNativeComponent): ViewModelProvider.Factory = + viewModelFactory { + initializer { + parentComponent + .attachPaymentSubcomponent + .initialState(AttachPaymentState()) + .build() + .viewModel + } + } private val PANE = Pane.ATTACH_LINKED_PAYMENT_ACCOUNT } @@ -124,4 +122,4 @@ internal class AttachPaymentViewModel @Inject constructor( internal data class AttachPaymentState( val linkPaymentAccount: Async = Uninitialized -) : MavericksState +) diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModelTest.kt index da5fad466d4..ec4e31078ca 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModelTest.kt @@ -79,7 +79,6 @@ internal class PartnerAuthViewModelTest { pane = Pane.PARTNER_AUTH, displayErrorScreen = true ) - } @Test From 38c29bc8dc5fb45b0fab966180605b70dc49de6f Mon Sep 17 00:00:00 2001 From: "Carlos M." Date: Mon, 25 Mar 2024 13:48:47 -0700 Subject: [PATCH 6/7] PR feedback. --- .../core/FinancialConnectionsViewModel.kt | 5 +++ .../accountpicker/AccountPickerViewModel.kt | 4 +-- .../partnerauth/PartnerAuthViewModel.kt | 2 +- .../FinancialConnectionsErrorRepository.kt | 8 ++--- .../repository/SuccessContentRepository.kt | 8 ++--- .../FinancialConnectionsSheetViewModelTest.kt | 32 +++++++++---------- .../AccountPickerViewModelTest.kt | 11 ++++--- .../InstitutionPickerViewModelTest.kt | 9 +++--- ...cialConnectionsSheetNativeViewModelTest.kt | 23 ++++++------- 9 files changed, 52 insertions(+), 50 deletions(-) diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/core/FinancialConnectionsViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/core/FinancialConnectionsViewModel.kt index a9420f5348d..4d6b25e8c71 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/core/FinancialConnectionsViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/core/FinancialConnectionsViewModel.kt @@ -42,6 +42,8 @@ internal abstract class FinancialConnectionsViewModel( } } + protected fun withState(action: (state: S) -> Unit) = stateFlow.value.let(action) + protected open fun onAsync( prop: KProperty1>, onSuccess: suspend (T) -> Unit = {}, @@ -77,3 +79,6 @@ internal sealed class Async( open operator fun invoke(): T? = value } + +fun , B, C> withState(viewModel: A, block: (B) -> C) = + block(viewModel.stateFlow.value) diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerViewModel.kt index 336fc505b2b..52909c16d28 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerViewModel.kt @@ -181,7 +181,7 @@ internal class AccountPickerViewModel @Inject constructor( ) } - fun onAccountClicked(account: PartnerAccount) = stateFlow.value.let { state -> + fun onAccountClicked(account: PartnerAccount) = withState { state -> state.payload()?.let { payload -> val selectedIds = state.selectedIds val newSelectedIds = when (payload.selectionMode) { @@ -237,7 +237,7 @@ internal class AccountPickerViewModel @Inject constructor( eventTracker.track(ClickLinkAccounts(PANE)) } FinancialConnections.emitEvent(name = Name.ACCOUNTS_SELECTED) - stateFlow.value.let { state -> + withState { state -> state.payload()?.let { submitAccounts( selectedIds = state.selectedIds, diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModel.kt index f7b4222d619..0546938e0b8 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/partnerauth/PartnerAuthViewModel.kt @@ -172,7 +172,7 @@ internal class PartnerAuthViewModel @Inject constructor( fun onLaunchAuthClick() { setState { copy(authenticationStatus = Loading(value = Status(Action.AUTHENTICATING))) } - stateFlow.value.let { state -> + withState { state -> val authSession = requireNotNull(state.payload()?.authSession) { "Payload shouldn't be null when the user launches the auth flow" } diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsErrorRepository.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsErrorRepository.kt index d4c1226fed2..dc1ecd9f23f 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsErrorRepository.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsErrorRepository.kt @@ -1,17 +1,15 @@ package com.stripe.android.financialconnections.repository import com.airbnb.mvrx.MavericksState -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.update internal class FinancialConnectionsErrorRepository { - private val state = MutableStateFlow(State()) + private var state = State() - suspend fun get() = state.value.error + fun get() = state.error fun update(reducer: State.() -> State) { - state.update { reducer(it) } + state = reducer(state) } fun set(error: Throwable) { diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/SuccessContentRepository.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/SuccessContentRepository.kt index be8abd6cfe7..ee7064764ac 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/SuccessContentRepository.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/SuccessContentRepository.kt @@ -2,8 +2,6 @@ package com.stripe.android.financialconnections.repository import com.stripe.android.financialconnections.repository.SuccessContentRepository.State import com.stripe.android.financialconnections.ui.TextResource -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.update import javax.inject.Inject internal interface SuccessContentRepository { @@ -17,11 +15,11 @@ internal interface SuccessContentRepository { internal class SuccessContentRepositoryImpl @Inject constructor() : SuccessContentRepository { - private val state = MutableStateFlow(State()) + private var state = State() - override suspend fun get() = state.value + override suspend fun get() = state override fun update(reducer: State.() -> State) { - state.update { reducer(it) } + state = reducer(state) } } diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModelTest.kt index 811e24970dc..fddc086c423 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/FinancialConnectionsSheetViewModelTest.kt @@ -14,6 +14,7 @@ import com.stripe.android.financialconnections.FinancialConnectionsSheetViewEffe import com.stripe.android.financialconnections.FinancialConnectionsSheetViewEffect.OpenAuthFlowWithUrl import com.stripe.android.financialconnections.analytics.FinancialConnectionsEventReporter import com.stripe.android.financialconnections.browser.BrowserManager +import com.stripe.android.financialconnections.core.withState import com.stripe.android.financialconnections.domain.FetchFinancialConnectionsSession import com.stripe.android.financialconnections.domain.FetchFinancialConnectionsSessionForToken import com.stripe.android.financialconnections.domain.SynchronizeFinancialConnectionsSession @@ -106,7 +107,7 @@ class FinancialConnectionsSheetViewModelTest { val viewModel = createViewModel(defaultInitialState) // Then - viewModel.stateFlow.value.let { + withState(viewModel) { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.NONE) require(it.viewEffect is FinishWithResult) require(it.viewEffect.result is Failed) @@ -161,12 +162,10 @@ class FinancialConnectionsSheetViewModelTest { ) // Then - viewModel.stateFlow.value.let { + withState(viewModel) { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.NONE) val viewEffect = it.viewEffect as FinishWithResult - assertThat(viewEffect.result).isEqualTo( - Completed(linkedAccountId = linkedAccountId) - ) + assertThat(viewEffect.result).isEqualTo(Completed(linkedAccountId = linkedAccountId)) } } } @@ -189,7 +188,7 @@ class FinancialConnectionsSheetViewModelTest { ) // Then - viewModel.stateFlow.value.let { + withState(viewModel) { val viewEffect = it.viewEffect as FinishWithResult assertThat(viewEffect.result).isInstanceOf(Failed::class.java) } @@ -233,7 +232,7 @@ class FinancialConnectionsSheetViewModelTest { // end auth flow viewModel.handleOnNewIntent(cancelIntent()) - viewModel.stateFlow.value.let { + withState(viewModel) { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.NONE) assertThat(it.viewEffect).isEqualTo(FinishWithResult(Canceled)) } @@ -260,7 +259,7 @@ class FinancialConnectionsSheetViewModelTest { viewModel.handleOnNewIntent(cancelIntent()) // Then - viewModel.stateFlow.value.let { + withState(viewModel) { require(it.viewEffect is FinishWithResult) require(it.viewEffect.result is Failed) assertThat(it.viewEffect.result.error).isInstanceOf(CustomManualEntryRequiredError::class.java) @@ -281,7 +280,7 @@ class FinancialConnectionsSheetViewModelTest { viewModel.handleOnNewIntent(errorIntent) // Then - viewModel.stateFlow.value.let { + withState(viewModel) { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.NONE) val viewEffect = it.viewEffect as FinishWithResult assertThat(viewEffect.result).isInstanceOf(Failed::class.java) @@ -304,7 +303,7 @@ class FinancialConnectionsSheetViewModelTest { viewModel.handleOnNewIntent(successIntent()) // Then - viewModel.stateFlow.value.let { + withState(viewModel) { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.NONE) val viewEffect = it.viewEffect as FinishWithResult assertThat(viewEffect.result).isEqualTo( @@ -331,7 +330,7 @@ class FinancialConnectionsSheetViewModelTest { viewModel.handleOnNewIntent(Intent().apply { data = Uri.parse(nativeRedirectUrl) }) // Then - viewModel.stateFlow.value.let { + withState(viewModel) { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.INTERMEDIATE_DEEPLINK) val viewEffect = it.viewEffect as OpenAuthFlowWithUrl assertThat(viewEffect.url).isEqualTo(aggregatorUrl) @@ -357,7 +356,7 @@ class FinancialConnectionsSheetViewModelTest { viewModel.handleOnNewIntent(Intent().apply { data = Uri.parse(returnUrl) }) // Then - viewModel.stateFlow.value.let { + withState(viewModel) { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.INTERMEDIATE_DEEPLINK) val viewEffect = it.viewEffect as OpenAuthFlowWithUrl assertThat(viewEffect.url).isEqualTo( @@ -381,7 +380,7 @@ class FinancialConnectionsSheetViewModelTest { viewModel.handleOnNewIntent(successIntent()) // Then - viewModel.stateFlow.value.let { + withState(viewModel) { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.NONE) val viewEffect = it.viewEffect as FinishWithResult assertThat(viewEffect.result).isEqualTo(Failed(apiException)) @@ -401,8 +400,7 @@ class FinancialConnectionsSheetViewModelTest { viewModel.handleOnNewIntent(successIntent()) // Then - // Then - viewModel.stateFlow.value.let { + withState(viewModel) { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.NONE) val viewEffect = it.viewEffect as FinishWithResult assertThat(viewEffect.result).isEqualTo(Failed(APIException())) @@ -429,7 +427,7 @@ class FinancialConnectionsSheetViewModelTest { viewModel.onResume() // Then - viewModel.stateFlow.value.let { + withState(viewModel) { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.ON_EXTERNAL_ACTIVITY) val viewEffect = it.viewEffect as FinishWithResult assertThat(viewEffect.result).isEqualTo(Canceled) @@ -456,7 +454,7 @@ class FinancialConnectionsSheetViewModelTest { viewModel.onBrowserActivityResult() // Then - viewModel.stateFlow.value.let { + withState(viewModel) { assertThat(it.webAuthFlowStatus).isEqualTo(AuthFlowStatus.ON_EXTERNAL_ACTIVITY) val viewEffect = it.viewEffect as FinishWithResult assertThat(viewEffect.result).isEqualTo(Canceled) diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerViewModelTest.kt index 342e4d00987..ece09b1851f 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/accountpicker/AccountPickerViewModelTest.kt @@ -9,6 +9,7 @@ import com.stripe.android.financialconnections.ApiKeyFixtures.partnerAccountList import com.stripe.android.financialconnections.ApiKeyFixtures.sessionManifest import com.stripe.android.financialconnections.ApiKeyFixtures.syncResponse import com.stripe.android.financialconnections.TestFinancialConnectionsAnalyticsTracker +import com.stripe.android.financialconnections.core.withState import com.stripe.android.financialconnections.domain.GetOrFetchSync import com.stripe.android.financialconnections.domain.PollAuthorizationSessionAccounts import com.stripe.android.financialconnections.domain.SelectAccounts @@ -67,7 +68,7 @@ internal class AccountPickerViewModelTest { val viewModel = buildViewModel(AccountPickerState()) - viewModel.stateFlow.value.let { state -> + withState(viewModel) { state -> assertEquals(state.payload()!!.skipAccountSelection, true) } } @@ -90,7 +91,7 @@ internal class AccountPickerViewModelTest { val viewModel = buildViewModel(AccountPickerState()) - viewModel.stateFlow.value.let { state -> + withState(viewModel) { state -> val payload = state.payload()!! assertEquals(payload.skipAccountSelection, true) assertEquals(payload.shouldSkipPane, true) @@ -117,7 +118,7 @@ internal class AccountPickerViewModelTest { val viewModel = buildViewModel(AccountPickerState()) - viewModel.stateFlow.value.let { state -> + withState(viewModel) { state -> assertEquals(state.payload()!!.userSelectedSingleAccountInInstitution, true) assertEquals(state.payload()!!.shouldSkipPane, true) } @@ -143,7 +144,7 @@ internal class AccountPickerViewModelTest { val viewModel = buildViewModel(AccountPickerState()) - viewModel.stateFlow.value.let { state -> + withState(viewModel) { state -> assertThat(state.selectedIds).isEqualTo(setOf("selectable")) } @@ -177,7 +178,7 @@ internal class AccountPickerViewModelTest { val viewModel = buildViewModel(AccountPickerState()) - viewModel.stateFlow.value.let { state -> + withState(viewModel) { state -> assertThat(state.selectedIds).isEqualTo(setOf("selectable_1", "selectable_2")) } diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/institutionpicker/InstitutionPickerViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/institutionpicker/InstitutionPickerViewModelTest.kt index 057f6709f84..cdba96eef43 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/features/institutionpicker/InstitutionPickerViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/features/institutionpicker/InstitutionPickerViewModelTest.kt @@ -6,6 +6,7 @@ import com.stripe.android.financialconnections.CoroutineTestRule import com.stripe.android.financialconnections.FinancialConnectionsSheet import com.stripe.android.financialconnections.TestFinancialConnectionsAnalyticsTracker import com.stripe.android.financialconnections.core.Async +import com.stripe.android.financialconnections.core.withState import com.stripe.android.financialconnections.domain.FeaturedInstitutions import com.stripe.android.financialconnections.domain.GetOrFetchSync import com.stripe.android.financialconnections.domain.PostAuthorizationSession @@ -93,7 +94,7 @@ internal class InstitutionPickerViewModelTest { val viewModel = buildViewModel(InstitutionPickerState()) - viewModel.stateFlow.value.let { state -> + withState(viewModel) { state -> assertEquals(state.payload()!!.featuredInstitutions, institutionResponse) assertIs(state.searchInstitutions) } @@ -120,7 +121,7 @@ internal class InstitutionPickerViewModelTest { val viewModel = buildViewModel(InstitutionPickerState()) - viewModel.stateFlow.value.let { state -> + withState(viewModel) { state -> assertTrue(state.payload() == null) handleError.assertError( error = error, @@ -169,7 +170,7 @@ internal class InstitutionPickerViewModelTest { viewModel.onQueryChanged(query) advanceUntilIdle() - viewModel.stateFlow.value.let { state -> + withState(viewModel) { state -> assertEquals(state.payload()!!.featuredInstitutions, featuredResults) assertEquals(state.searchInstitutions()!!, searchResults) eventTracker.assertContainsEvent( @@ -192,7 +193,7 @@ internal class InstitutionPickerViewModelTest { viewModel.onQueryChanged(query) advanceUntilIdle() - viewModel.stateFlow.value.let { state -> + withState(viewModel) { state -> verifyNoInteractions(searchInstitutions) assertTrue(eventTracker.sentEvents.none { it.eventName == "linked_accounts.search.succeeded" }) assertEquals(state.searchInstitutions()!!.data, emptyList()) diff --git a/financial-connections/src/test/java/com/stripe/android/financialconnections/presentation/FinancialConnectionsSheetNativeViewModelTest.kt b/financial-connections/src/test/java/com/stripe/android/financialconnections/presentation/FinancialConnectionsSheetNativeViewModelTest.kt index 209703dfe22..c45801aa860 100644 --- a/financial-connections/src/test/java/com/stripe/android/financialconnections/presentation/FinancialConnectionsSheetNativeViewModelTest.kt +++ b/financial-connections/src/test/java/com/stripe/android/financialconnections/presentation/FinancialConnectionsSheetNativeViewModelTest.kt @@ -14,6 +14,7 @@ import com.stripe.android.financialconnections.FinancialConnectionsSheet import com.stripe.android.financialconnections.analytics.FinancialConnectionsEvent import com.stripe.android.financialconnections.analytics.FinancialConnectionsEvent.Metadata import com.stripe.android.financialconnections.analytics.FinancialConnectionsEvent.Name +import com.stripe.android.financialconnections.core.withState import com.stripe.android.financialconnections.domain.CompleteFinancialConnectionsSession import com.stripe.android.financialconnections.domain.NativeAuthFlowCoordinator import com.stripe.android.financialconnections.domain.NativeAuthFlowCoordinator.Message.Complete @@ -88,7 +89,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { messagesFlow.emit(Complete(EarlyTerminationCause.USER_INITIATED_WITH_CUSTOM_MANUAL_ENTRY)) - viewModel.stateFlow.value.let { + withState(viewModel) { require(it.viewEffect is Finish) require(it.viewEffect.result is Failed) assertThat(it.viewEffect.result.error).isInstanceOf(CustomManualEntryRequiredError::class.java) @@ -108,7 +109,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { messagesFlow.emit(Complete(null)) - viewModel.stateFlow.value.let { + withState(viewModel) { require(it.viewEffect is Finish) require(it.viewEffect.result is Completed) } @@ -134,7 +135,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { messagesFlow.emit(Complete(null)) - viewModel.stateFlow.value.let { + withState(viewModel) { require(it.viewEffect is Finish) require(it.viewEffect.result is Canceled) } @@ -166,7 +167,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { messagesFlow.emit(Complete(null)) - viewModel.stateFlow.value.let { + withState(viewModel) { require(it.viewEffect is Finish) require(it.viewEffect.result is Failed) } @@ -187,7 +188,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { val intent = intent("stripe://auth-redirect/$applicationId?status=success") viewModel.handleOnNewIntent(intent) - viewModel.stateFlow.value.let { + withState(viewModel) { assertThat(it.webAuthFlow).isEqualTo(WebAuthFlowState.Success(intent.data!!.toString())) } } @@ -202,7 +203,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { ) viewModel.handleOnNewIntent(intent) - viewModel.stateFlow.value.let { + withState(viewModel) { val webAuthFlow = it.webAuthFlow assertIs(webAuthFlow) assertThat(webAuthFlow.reason).isEqualTo(errorReason) @@ -218,7 +219,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { ) viewModel.handleOnNewIntent(intent) - viewModel.stateFlow.value.let { + withState(viewModel) { val webAuthFlow = it.webAuthFlow assertIs(webAuthFlow) } @@ -233,7 +234,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { ) viewModel.handleOnNewIntent(intent) - viewModel.stateFlow.value.let { + withState(viewModel) { val webAuthFlow = it.webAuthFlow assertIs(webAuthFlow) } @@ -246,7 +247,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { val intent = intent("stripe://auth-redirect/$applicationId?status=unknown") viewModel.handleOnNewIntent(intent) - viewModel.stateFlow.value.let { + withState(viewModel) { val webAuthFlow = it.webAuthFlow assertIs(webAuthFlow) } @@ -259,7 +260,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { val intent = intent("stripe://auth-redirect/$applicationId?status=cancel") viewModel.handleOnNewIntent(intent) - viewModel.stateFlow.value.let { + withState(viewModel) { val webAuthFlow = it.webAuthFlow assertIs(webAuthFlow) } @@ -272,7 +273,7 @@ internal class FinancialConnectionsSheetNativeViewModelTest { val intent = intent("stripe://auth-redirect/other-app-id?code=success") viewModel.handleOnNewIntent(intent) - viewModel.stateFlow.value.let { + withState(viewModel) { val webAuthFlow = it.webAuthFlow assertIs(webAuthFlow) } From c982448157d3372b94622d80446d4f84b9e0be41 Mon Sep 17 00:00:00 2001 From: "Carlos M." Date: Mon, 25 Mar 2024 15:01:01 -0700 Subject: [PATCH 7/7] Api dump. --- .../financialconnections/core/FinancialConnectionsViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/core/FinancialConnectionsViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/core/FinancialConnectionsViewModel.kt index 4d6b25e8c71..60bc999be06 100644 --- a/financial-connections/src/main/java/com/stripe/android/financialconnections/core/FinancialConnectionsViewModel.kt +++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/core/FinancialConnectionsViewModel.kt @@ -80,5 +80,5 @@ internal sealed class Async( open operator fun invoke(): T? = value } -fun , B, C> withState(viewModel: A, block: (B) -> C) = +internal fun , B, C> withState(viewModel: A, block: (B) -> C) = block(viewModel.stateFlow.value)