Skip to content

Commit

Permalink
[connect] Use AndroidX Navigation in Example app (#9767)
Browse files Browse the repository at this point in the history
* refactor using androidx navigation compose

* use bottom sheet in MainActivity; safe nav up

* ruby scripts/dependencies/update_transitive_dependencies.rb
  • Loading branch information
lng-stripe authored Dec 11, 2024
1 parent 72e13bc commit 5d21a09
Show file tree
Hide file tree
Showing 13 changed files with 280 additions and 304 deletions.
1 change: 1 addition & 0 deletions connect-example/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies {
implementation libs.androidx.browser
implementation libs.androidx.fragment
implementation libs.androidx.fragmentCompose
implementation libs.androidx.hiltNavigationCompose
implementation libs.androidx.lifecycle
implementation libs.androidx.savedState
implementation libs.androidx.viewModel
Expand Down
53 changes: 32 additions & 21 deletions connect-example/dependencies/dependencies.txt
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@
| | | +--- androidx.lifecycle:lifecycle-runtime:2.8.6 (c)
| | | +--- androidx.lifecycle:lifecycle-runtime-ktx:2.8.6 (c)
| | | +--- androidx.lifecycle:lifecycle-viewmodel:2.8.6 (c)
| | | +--- androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.6 (c)
| | | +--- androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6 (c)
| | | +--- androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.6 (c)
| | | +--- androidx.lifecycle:lifecycle-common:2.8.6 (c)
| | | +--- androidx.lifecycle:lifecycle-process:2.8.6 (c)
| | | \--- androidx.lifecycle:lifecycle-livedata:2.8.6 (c)
Expand Down Expand Up @@ -186,8 +186,8 @@
| | | | +--- androidx.lifecycle:lifecycle-runtime:2.8.6 (c)
| | | | +--- androidx.lifecycle:lifecycle-runtime-ktx:2.8.6 (c)
| | | | +--- androidx.lifecycle:lifecycle-viewmodel:2.8.6 (c)
| | | | +--- androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6 (c)
| | | | +--- androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6 (c)
| | | | +--- androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6 (c)
| | | | +--- androidx.lifecycle:lifecycle-common:2.8.6 (c)
| | | | +--- androidx.lifecycle:lifecycle-process:2.8.6 (c)
| | | | \--- androidx.lifecycle:lifecycle-livedata:2.8.6 (c)
Expand Down Expand Up @@ -637,8 +637,8 @@
| | | \--- androidx.navigation:navigation-common:2.7.7 (c)
| | +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.21 (*)
| | +--- androidx.navigation:navigation-runtime-ktx:2.7.7 (c)
| | +--- androidx.navigation:navigation-common-ktx:2.7.7 (c)
| | +--- androidx.navigation:navigation-runtime:2.7.7 (c)
| | +--- androidx.navigation:navigation-common-ktx:2.7.7 (c)
| | \--- androidx.navigation:navigation-common:2.7.7 (c)
| +--- com.google.accompanist:accompanist-systemuicontroller:0.34.0
| | +--- androidx.core:core-ktx:1.8.0 -> 1.13.0 (*)
Expand Down Expand Up @@ -674,6 +674,34 @@
| +--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.21 (*)
| +--- androidx.fragment:fragment:1.8.4 (c)
| \--- androidx.fragment:fragment-ktx:1.8.4 (c)
+--- androidx.hilt:hilt-navigation-compose:1.2.0
| +--- androidx.compose.runtime:runtime:1.0.1 -> 1.6.8 (*)
| +--- androidx.compose.ui:ui:1.0.1 -> 1.6.8 (*)
| +--- androidx.hilt:hilt-navigation:1.2.0
| | +--- androidx.annotation:annotation:1.1.0 -> 1.9.0 (*)
| | +--- androidx.navigation:navigation-runtime:2.5.1 -> 2.7.7 (*)
| | +--- com.google.dagger:hilt-android:2.49 -> 2.52
| | | +--- com.google.dagger:dagger:2.52 (*)
| | | +--- com.google.dagger:dagger-lint-aar:2.52
| | | +--- com.google.dagger:hilt-core:2.52
| | | | +--- com.google.dagger:dagger:2.52 (*)
| | | | +--- com.google.code.findbugs:jsr305:3.0.2
| | | | \--- javax.inject:javax.inject:1
| | | +--- com.google.code.findbugs:jsr305:3.0.2
| | | +--- androidx.activity:activity:1.5.1 -> 1.8.2 (*)
| | | +--- androidx.annotation:annotation:1.3.0 -> 1.9.0 (*)
| | | +--- androidx.annotation:annotation-experimental:1.3.1 -> 1.4.0 (*)
| | | +--- androidx.fragment:fragment:1.5.1 -> 1.8.4 (*)
| | | +--- androidx.lifecycle:lifecycle-common:2.5.1 -> 2.8.6 (*)
| | | +--- androidx.lifecycle:lifecycle-viewmodel:2.5.1 -> 2.8.6 (*)
| | | +--- androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1 -> 2.8.6 (*)
| | | +--- androidx.savedstate:savedstate:1.2.0 -> 1.2.1 (*)
| | | +--- javax.inject:javax.inject:1
| | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -> 2.0.21 (*)
| | \--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.21 (*)
| +--- androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1 -> 2.8.6 (*)
| +--- androidx.navigation:navigation-compose:2.5.1 -> 2.7.7 (*)
| \--- org.jetbrains.kotlin:kotlin-stdlib:1.8.22 -> 2.0.21 (*)
+--- androidx.lifecycle:lifecycle-runtime-ktx:2.8.6 (*)
+--- androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.6 (*)
+--- androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6 (*)
Expand All @@ -697,22 +725,5 @@
+--- androidx.activity:activity-compose:1.8.2 (*)
+--- androidx.navigation:navigation-compose:2.7.7 (*)
+--- com.google.accompanist:accompanist-systemuicontroller:0.34.0 (*)
+--- com.google.dagger:hilt-android:2.52
| +--- com.google.dagger:dagger:2.52 (*)
| +--- com.google.dagger:dagger-lint-aar:2.52
| +--- com.google.dagger:hilt-core:2.52
| | +--- com.google.dagger:dagger:2.52 (*)
| | +--- com.google.code.findbugs:jsr305:3.0.2
| | \--- javax.inject:javax.inject:1
| +--- com.google.code.findbugs:jsr305:3.0.2
| +--- androidx.activity:activity:1.5.1 -> 1.8.2 (*)
| +--- androidx.annotation:annotation:1.3.0 -> 1.9.0 (*)
| +--- androidx.annotation:annotation-experimental:1.3.1 -> 1.4.0 (*)
| +--- androidx.fragment:fragment:1.5.1 -> 1.8.4 (*)
| +--- androidx.lifecycle:lifecycle-common:2.5.1 -> 2.8.6 (*)
| +--- androidx.lifecycle:lifecycle-viewmodel:2.5.1 -> 2.8.6 (*)
| +--- androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1 -> 2.8.6 (*)
| +--- androidx.savedstate:savedstate:1.2.0 -> 1.2.1 (*)
| +--- javax.inject:javax.inject:1
| \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -> 2.0.21 (*)
+--- com.google.dagger:hilt-android:2.52 (*)
\--- org.jetbrains.kotlin:kotlin-parcelize-runtime:2.0.21 (*)
Original file line number Diff line number Diff line change
Expand Up @@ -2,137 +2,52 @@ package com.stripe.android.connect.example

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import com.stripe.android.connect.PrivateBetaConnectSDK
import com.stripe.android.connect.example.core.Success
import com.stripe.android.connect.example.core.then
import com.stripe.android.connect.example.ui.appearance.AppearanceView
import com.stripe.android.connect.example.ui.common.ConnectExampleScaffold
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.example.core.safeNavigateUp
import com.stripe.android.connect.example.ui.common.ConnectSdkExampleTheme
import com.stripe.android.connect.example.ui.componentpicker.ComponentPickerList
import com.stripe.android.connect.example.ui.componentpicker.ComponentPickerContent
import com.stripe.android.connect.example.ui.embeddedcomponentmanagerloader.EmbeddedComponentLoaderViewModel
import com.stripe.android.connect.example.ui.embeddedcomponentmanagerloader.EmbeddedComponentManagerLoader
import com.stripe.android.connect.example.ui.settings.SettingsView
import com.stripe.android.connect.example.ui.settings.SettingsViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch

@OptIn(PrivateBetaConnectSDK::class)
@AndroidEntryPoint
class MainActivity : ComponentActivity() {

private val viewModel: EmbeddedComponentLoaderViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
val viewModel = hiltViewModel<EmbeddedComponentLoaderViewModel>()
val navController = rememberNavController()
ConnectSdkExampleTheme {
ComponentPickerContent()
}
}
}

@Suppress("LongMethod")
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun ComponentPickerContent() {
val state by viewModel.state.collectAsState()
val embeddedComponentAsync = state.embeddedComponentManagerAsync

val sheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
skipHalfExpanded = true,
)
var sheetType by rememberSaveable { mutableStateOf(SheetType.SETTINGS) }
val coroutineScope = rememberCoroutineScope()

ConnectExampleScaffold(
title = stringResource(R.string.connect_sdk_example),
actions = (embeddedComponentAsync is Success).then {
{
IconButton(
onClick = {
coroutineScope.launch {
if (!sheetState.isVisible) {
sheetType = SheetType.SETTINGS
sheetState.show()
} else {
sheetState.hide()
}
}
}
) {
Icon(
imageVector = Icons.Default.Settings,
contentDescription = stringResource(R.string.settings),
NavHost(navController = navController, startDestination = MainDestination.ComponentPicker) {
composable(MainDestination.ComponentPicker) {
ComponentPickerContent(
viewModel = viewModel,
openSettings = { navController.navigate(route = MainDestination.Settings) },
)
}
IconButton(
onClick = {
coroutineScope.launch {
if (!sheetState.isVisible) {
sheetType = SheetType.APPEARANCE
sheetState.show()
} else {
sheetState.hide()
}
}
}
) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = stringResource(R.string.customize_appearance),
)
}
}
} ?: { },
modalSheetState = sheetState,
modalContent = (embeddedComponentAsync is Success).then {
{
BackHandler(enabled = sheetState.isVisible) {
coroutineScope.launch { sheetState.hide() }
}
when (sheetType) {
SheetType.SETTINGS -> SettingsView(
onDismiss = { coroutineScope.launch { sheetState.hide() } },
composable(MainDestination.Settings) {
val settingsViewModel = hiltViewModel<SettingsViewModel>()
SettingsView(
viewModel = settingsViewModel,
onDismiss = { navController.safeNavigateUp() },
onReloadRequested = viewModel::reload,
)
SheetType.APPEARANCE -> AppearanceView(
onDismiss = { coroutineScope.launch { sheetState.hide() } },
)
}
}
},
) {
EmbeddedComponentManagerLoader(
embeddedComponentAsync = embeddedComponentAsync,
reload = viewModel::reload,
) {
ComponentPickerList()
}
}
}
}

private enum class SheetType {
SETTINGS,
APPEARANCE,
}
@Suppress("ConstPropertyName")
private object MainDestination {
const val ComponentPicker = "ComponentPicker"
const val Settings = "Settings"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.stripe.android.connect.example.core

import androidx.lifecycle.Lifecycle
import androidx.navigation.NavHostController

/**
* Safer [NavHostController.navigateUp] that prevents navigating past the previous screen,
* typically due to accidental double-clicks.
*/
fun NavHostController.safeNavigateUp() {
if (currentBackStackEntry?.lifecycle?.currentState == Lifecycle.State.RESUMED) {
navigateUp()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,27 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.RadioButton
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.stripe.android.connect.example.R
import com.stripe.android.connect.example.ui.common.ConnectExampleScaffold

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun AppearanceView(
onDismiss: () -> Unit,
viewModel: AppearanceViewModel = viewModel()
viewModel: AppearanceViewModel,
onDismiss: () -> Unit
) {
val state by viewModel.state.collectAsState()

Expand All @@ -38,7 +35,7 @@ fun AppearanceView(
navigationIcon = {
IconButton(onClick = onDismiss) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.cancel)
)
}
Expand Down
Loading

0 comments on commit 5d21a09

Please sign in to comment.