Skip to content

Split app to 2 screens with Navigation3 and NavigationStack #73

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

Merged
merged 2 commits into from
Jun 25, 2025
Merged

Conversation

cartland
Copy link
Contributor

This commit introduces the following changes:

Android App:

  • Implemented basic navigation between ListScreen and a new CartScreen using Navigation 3.
  • ListScreen now displays a "View Cart" button showing the item count.
  • CartScreen is a new Composable that displays the items in the cart.

iOS App:

  • Updated ContentView to include a navigation link to CartView.
  • The "View Cart" link on ContentView now displays the cart item count.
  • CartView now directly observes cartViewModel.cartUiState.
  • Removed the expand/collapse functionality from CartView and made CartDetailsView always visible within a ScrollView.

Shared Code:

  • Introduced a new CartViewModel to manage the state of the shopping cart, separating concerns from MainViewModel.
  • MainViewModel's HomeUiState now includes cartItemCount. The previous cartUiState and its direct cart details have been removed from MainViewModel.
  • Renamed IOSViewModelOwner to IOSViewModelStoreOwner and updated its methods to provide MainViewModel and CartViewModel.
  • Updated Android compile SDK to 36.
  • Added Kotlinx Serialization plugin.

Dependency Updates:

  • Added androidx.navigation3 and kotlinx-serialization-core dependencies.

This commit introduces the following changes:

**Android App:**
- Implemented basic navigation between `ListScreen` and a new `CartScreen` using Navigation 3.
- `ListScreen` now displays a "View Cart" button showing the item count.
- `CartScreen` is a new Composable that displays the items in the cart.

**iOS App:**
- Updated `ContentView` to include a navigation link to `CartView`.
- The "View Cart" link on `ContentView` now displays the cart item count.
- `CartView` now directly observes `cartViewModel.cartUiState`.
- Removed the expand/collapse functionality from `CartView` and made `CartDetailsView` always visible within a `ScrollView`.

**Shared Code:**
- Introduced a new `CartViewModel` to manage the state of the shopping cart, separating concerns from `MainViewModel`.
- `MainViewModel`'s `HomeUiState` now includes `cartItemCount`. The previous `cartUiState` and its direct cart details have been removed from `MainViewModel`.
- Renamed `IOSViewModelOwner` to `IOSViewModelStoreOwner` and updated its methods to provide `MainViewModel` and `CartViewModel`.
- Updated Android compile SDK to 36.
- Added Kotlinx Serialization plugin.

**Dependency Updates:**
- Added `androidx.navigation3` and `kotlinx-serialization-core` dependencies.
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @cartland, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the application's architecture by introducing multi-screen navigation on both Android and iOS platforms. It achieves this by separating the cart's business logic into a dedicated ViewModel in the shared module, leading to cleaner code and improved maintainability. The UI has been adapted on both platforms to support these new navigation flows and display relevant cart information.

Highlights

  • Multi-Screen Navigation Implementation: Introduced multi-screen navigation for both Android and iOS applications. Android now uses Navigation3 to navigate between a ListScreen and a new CartScreen, while iOS utilizes NavigationStack to link ContentView to a new CartView.
  • Cart Logic Separation and ViewModel Refactoring: Extracted cart-specific state management and logic into a new CartViewModel within the shared Kotlin Multiplatform module. This refactors MainViewModel to focus solely on the main list functionality, with HomeUiState now including only the cartItemCount.
  • Platform-Specific UI Adjustments: Updated Android's ListScreen to remove direct cart display and integrate with the new navigation flow, simplifying FruittieItem UI. On iOS, CartView was refactored to directly observe the new CartViewModel and its cart details are now always visible within a ScrollView, removing previous expand/collapse functionality.
  • Dependency and SDK Updates: Upgraded the Android compile SDK to 36 across both the Android app and shared modules. Added necessary androidx.navigation3 and kotlinx-serialization-core dependencies, and updated iOS deployment target to 16.6.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request splits the UI into two screens, ListScreen/ContentView and CartScreen/CartView, for Android and iOS respectively, and introduces CartViewModel to separate concerns. The use of Navigation3 and NavigationStack is a welcome improvement.

Suggestions include refining the navigation logic, improving UI performance by reducing redundant observers, and enhancing maintainability by centralizing state calculations in the ViewModel.

entry<CartScreenKey> {
CartScreen(
onNavBarBack = {
backStack.removeIf { it is CartScreenKey }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using removeIf here will remove all CartScreenKey entries from the back stack. To simply go back to the previous screen, use removeLastOrNull() instead.

Suggested change
backStack.removeIf { it is CartScreenKey }
backStack.removeLastOrNull()

@@ -110,34 +97,33 @@ fun ListScreen() {
) { paddingValues ->
Column(
modifier = Modifier
.padding(horizontal = 16.dp)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The padding(horizontal = 16.dp) is redundant because start = 16.dp and end = 16.dp are set in the subsequent padding modifier. Remove the first padding call.

            modifier = Modifier
                // Support edge-to-edge (required on Android 15)
                // https://developer.android.com/develop/ui/compose/layouts/insets#inset-size
                .padding(
                    // Draw to bottom edge. LazyColumn adds a Spacer for WindowInsets.systemBars.
                    // No bottom padding.
                    top = paddingValues.calculateTopPadding(),
                    start = 16.dp,
                    end = 16.dp,
                )

Comment on lines +37 to 49
Observing(mainViewModel.homeUiState) { homeUIState in
ScrollView {
LazyVStack {
ForEach(homeUIState.fruitties, id: \.self) { value in
FruittieView(fruittie: value, addToCart: { fruittie in
Task {
mainViewModel.addItemToCart(fruittie: fruittie)
}
})
}
})
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There are two Observing calls on the same mainViewModel.homeUiState. This creates two subscriptions to the same flow. Use a single Observing block that wraps the views that depend on the state.

        NavigationStack {
            VStack {
                Text("Fruitties").font(.largeTitle).fontWeight(.bold)
                Observing(mainViewModel.homeUiState) { homeUIState in
                    NavigationLink {
                        CartView(cartViewModel: cartViewModel)
                    } label: {
                        let total = homeUIState.cartItemCount
                        Text("View Cart (\(total))")
                    }
                    ScrollView {
                        LazyVStack {
                            ForEach(homeUIState.fruitties, id: \.self) { value in
                                FruittieView(fruittie: value, addToCart: { fruittie in
                                    Task {
                                        mainViewModel.addItemToCart(fruittie: fruittie)
                                    }
                                })
                            }
                        }
                    }
                }
            }
        }

Comment on lines 37 to 44
val cartUiState: StateFlow<CartUiState> =
repository.cartDetails
.map { CartUiState(it) }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS),
initialValue = CartUiState(),
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calculate the total item count here and store it in the CartUiState to avoid duplicate calculations in both Android and iOS UI code.

You'll also need to update CartUiState (at lines 67-69) to include the totalItemCount property:

data class CartUiState(
    val cartDetails: List<CartItemDetails> = listOf(),
    val totalItemCount: Int = 0,
)
    val cartUiState: StateFlow<CartUiState> =
        repository.cartDetails
            .map { details ->
                CartUiState(
                    cartDetails = details,
                    totalItemCount = details.sumOf { it.count }
                )
            }
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS),
                initialValue = CartUiState(),
            )

This commit refactors the calculation of the total number of items in the cart.
Previously, the total item count was calculated independently in both the Android `CartScreen.kt` and iOS `CartView.swift`.

This change moves the calculation into the shared `CartViewModel.kt`. A new `totalItemCount` property is added to `CartUiState`, which is now populated within the `map` function that transforms the `repository.cartDetails` flow.

This centralization simplifies the UI components and ensures consistency in how the total item count is determined across platforms.
@mlykotom mlykotom merged commit 92a9de0 into main Jun 25, 2025
4 of 5 checks passed
@mlykotom mlykotom deleted the nav3 branch June 25, 2025 21:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants