-
Notifications
You must be signed in to change notification settings - Fork 658
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[connect] Cleanup EmbeddedComponentManager
scope and setup
#9809
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.stripe.android.connect.example | ||
|
||
import android.os.Bundle | ||
import androidx.activity.ComponentActivity | ||
import androidx.activity.viewModels | ||
import androidx.annotation.CallSuper | ||
import com.stripe.android.connect.EmbeddedComponentManager | ||
import com.stripe.android.connect.PrivateBetaConnectSDK | ||
import com.stripe.android.connect.example.ui.embeddedcomponentmanagerloader.EmbeddedComponentLoaderViewModel | ||
import dagger.hilt.android.AndroidEntryPoint | ||
|
||
@OptIn(PrivateBetaConnectSDK::class) | ||
@AndroidEntryPoint | ||
abstract class BaseActivity : ComponentActivity() { | ||
|
||
protected val loaderViewModel: EmbeddedComponentLoaderViewModel by viewModels() | ||
|
||
@CallSuper | ||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
|
||
EmbeddedComponentManager.onActivityCreate(this) | ||
lifecycle.addObserver(loaderViewModel) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,26 +14,22 @@ import androidx.compose.material.ModalBottomSheetLayout | |
import androidx.compose.material.ModalBottomSheetValue | ||
import androidx.compose.material.rememberModalBottomSheetState | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.LaunchedEffect | ||
import androidx.compose.runtime.collectAsState | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.rememberCoroutineScope | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.platform.LocalContext | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.compose.ui.viewinterop.AndroidView | ||
import androidx.fragment.app.FragmentActivity | ||
import androidx.hilt.navigation.compose.hiltViewModel | ||
import androidx.navigation.compose.NavHost | ||
import androidx.navigation.compose.composable | ||
import androidx.navigation.compose.rememberNavController | ||
import com.stripe.android.connect.EmbeddedComponentManager | ||
import com.stripe.android.connect.PrivateBetaConnectSDK | ||
import com.stripe.android.connect.example.BaseActivity | ||
import com.stripe.android.connect.example.core.Async | ||
import com.stripe.android.connect.example.core.Success | ||
import com.stripe.android.connect.example.core.then | ||
import com.stripe.android.connect.example.data.SettingsService | ||
import com.stripe.android.connect.example.ui.appearance.AppearanceInfo | ||
import com.stripe.android.connect.example.ui.appearance.AppearanceView | ||
import com.stripe.android.connect.example.ui.appearance.AppearanceViewModel | ||
import com.stripe.android.connect.example.ui.embeddedcomponentmanagerloader.EmbeddedComponentLoaderViewModel | ||
|
@@ -42,17 +38,10 @@ import com.stripe.android.connect.example.ui.settings.SettingsViewModel | |
import com.stripe.android.connect.example.ui.settings.settingsComposables | ||
import dagger.hilt.android.AndroidEntryPoint | ||
import kotlinx.coroutines.launch | ||
import javax.inject.Inject | ||
|
||
@Suppress("ConstPropertyName") | ||
private object BasicComponentExampleDestination { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👀 nit: moved this below the Activity definition |
||
const val Component = "Component" | ||
const val Settings = "Settings" | ||
} | ||
|
||
@OptIn(PrivateBetaConnectSDK::class) | ||
@AndroidEntryPoint | ||
abstract class BasicExampleComponentActivity : FragmentActivity() { | ||
abstract class BasicExampleComponentActivity : BaseActivity() { | ||
|
||
@get:StringRes | ||
abstract val titleRes: Int | ||
|
@@ -61,14 +50,9 @@ abstract class BasicExampleComponentActivity : FragmentActivity() { | |
|
||
abstract fun createComponentView(context: Context, embeddedComponentManager: EmbeddedComponentManager): View | ||
|
||
@Inject | ||
lateinit var settingsService: SettingsService | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
|
||
EmbeddedComponentManager.onActivityCreate(this@BasicExampleComponentActivity) | ||
|
||
val settings = settingsViewModel.state.value | ||
val enableEdgeToEdge = settings.presentationSettings.enableEdgeToEdge | ||
if (enableEdgeToEdge) { | ||
|
@@ -77,13 +61,12 @@ abstract class BasicExampleComponentActivity : FragmentActivity() { | |
|
||
setContent { | ||
BackHandler(onBack = ::finish) | ||
val viewModel = hiltViewModel<EmbeddedComponentLoaderViewModel>(this@BasicExampleComponentActivity) | ||
val navController = rememberNavController() | ||
ConnectSdkExampleTheme { | ||
NavHost(navController = navController, startDestination = BasicComponentExampleDestination.Component) { | ||
composable(BasicComponentExampleDestination.Component) { | ||
ExampleComponentContent( | ||
viewModel = viewModel, | ||
viewModel = loaderViewModel, | ||
enableEdgeToEdge = enableEdgeToEdge, | ||
openSettings = { navController.navigate(BasicComponentExampleDestination.Settings) }, | ||
) | ||
|
@@ -175,16 +158,15 @@ abstract class BasicExampleComponentActivity : FragmentActivity() { | |
openSettings = openSettings, | ||
reload = reload, | ||
) { embeddedComponentManager -> | ||
val context = LocalContext.current | ||
LaunchedEffect(context) { | ||
val appearanceInfo = settingsService.getAppearanceId() | ||
?.let { AppearanceInfo.getAppearance(it, context).appearance } | ||
?: return@LaunchedEffect | ||
embeddedComponentManager.update(appearanceInfo) | ||
} | ||
AndroidView(modifier = Modifier.fillMaxSize(), factory = { | ||
createComponentView(it, embeddedComponentManager) | ||
}) | ||
} | ||
} | ||
} | ||
|
||
@Suppress("ConstPropertyName") | ||
private object BasicComponentExampleDestination { | ||
const val Component = "Component" | ||
const val Settings = "Settings" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,30 @@ | ||
package com.stripe.android.connect.example.ui.embeddedcomponentmanagerloader | ||
|
||
import androidx.activity.ComponentActivity | ||
import androidx.lifecycle.DefaultLifecycleObserver | ||
import androidx.lifecycle.LifecycleOwner | ||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.lifecycleScope | ||
import androidx.lifecycle.viewModelScope | ||
import com.github.kittinunf.fuel.core.FuelError | ||
import com.stripe.android.connect.PrivateBetaConnectSDK | ||
import com.stripe.android.connect.example.BuildConfig | ||
import com.stripe.android.connect.example.core.Fail | ||
import com.stripe.android.connect.example.core.Loading | ||
import com.stripe.android.connect.example.core.Success | ||
import com.stripe.android.connect.example.data.EmbeddedComponentManagerProvider | ||
import com.stripe.android.connect.example.data.EmbeddedComponentManagerFactory | ||
import com.stripe.android.connect.example.data.EmbeddedComponentService | ||
import com.stripe.android.connect.example.data.SettingsService | ||
import com.stripe.android.connect.example.ui.appearance.AppearanceInfo | ||
import com.stripe.android.core.Logger | ||
import dagger.hilt.android.lifecycle.HiltViewModel | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.flow.asStateFlow | ||
import kotlinx.coroutines.flow.collectLatest | ||
import kotlinx.coroutines.flow.combine | ||
import kotlinx.coroutines.flow.filterNotNull | ||
import kotlinx.coroutines.flow.map | ||
import kotlinx.coroutines.flow.update | ||
import kotlinx.coroutines.launch | ||
import javax.inject.Inject | ||
|
@@ -23,8 +33,9 @@ import javax.inject.Inject | |
@HiltViewModel | ||
class EmbeddedComponentLoaderViewModel @Inject constructor( | ||
private val embeddedComponentService: EmbeddedComponentService, | ||
private val embeddedComponentManagerProvider: EmbeddedComponentManagerProvider, | ||
) : ViewModel() { | ||
private val embeddedComponentManagerFactory: EmbeddedComponentManagerFactory, | ||
private val settingsService: SettingsService, | ||
) : ViewModel(), DefaultLifecycleObserver { | ||
|
||
private val logger: Logger = Logger.getInstance(enableLogging = BuildConfig.DEBUG) | ||
private val loggingTag = this::class.java.simpleName | ||
|
@@ -33,27 +44,42 @@ class EmbeddedComponentLoaderViewModel @Inject constructor( | |
val state: StateFlow<EmbeddedComponentManagerLoaderState> = _state.asStateFlow() | ||
|
||
init { | ||
initializeManager() | ||
loadManagerIfNecessary() | ||
} | ||
|
||
// Public methods | ||
|
||
fun reload() { | ||
loadManager() | ||
} | ||
|
||
// Private methods | ||
override fun onCreate(owner: LifecycleOwner) { | ||
val activity = owner as? ComponentActivity | ||
?: return | ||
|
||
private fun initializeManager() { | ||
val manager = embeddedComponentManagerProvider.provideEmbeddedComponentManager() | ||
if (manager == null) { | ||
loadManager() | ||
return | ||
// Bind appearance settings to the manager. | ||
activity.lifecycleScope.launch { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👀 Important that we bind in the Activity scope and not ViewModel scope, otherwise there'd be a memory leak |
||
val managerFlow = _state | ||
.map { it.embeddedComponentManagerAsync() } | ||
.filterNotNull() | ||
val appearanceFlow = settingsService.getAppearanceIdFlow() | ||
.filterNotNull() | ||
.map { id -> AppearanceInfo.getAppearance(id, activity).appearance } | ||
combine(managerFlow, appearanceFlow, ::Pair).collectLatest { (manager, appearance) -> | ||
logger.debug("($loggingTag) Updating appearance in $activity") | ||
manager.update(appearance) | ||
} | ||
} | ||
} | ||
|
||
_state.update { | ||
it.copy(embeddedComponentManagerAsync = Success(manager)) | ||
private fun loadManagerIfNecessary() { | ||
if (_state.value.embeddedComponentManagerAsync() != null) { | ||
return | ||
} | ||
val manager = embeddedComponentManagerFactory.createEmbeddedComponentManager() | ||
if (manager != null) { | ||
_state.update { it.copy(embeddedComponentManagerAsync = Success(manager)) } | ||
return | ||
} | ||
loadManager() | ||
} | ||
|
||
private fun loadManager() { | ||
|
@@ -68,7 +94,7 @@ class EmbeddedComponentLoaderViewModel @Inject constructor( | |
embeddedComponentService.getAccounts() | ||
|
||
// initialize the SDK, or throw an error if we're unable to | ||
val manager = embeddedComponentManagerProvider.provideEmbeddedComponentManager() | ||
val manager = embeddedComponentManagerFactory.createEmbeddedComponentManager() | ||
val async = if (manager != null) { | ||
Success(manager) | ||
} else { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀 Moved to
BaseActivity#onCreate()