Skip to content

Commit

Permalink
feat: Migrate to type-safe navigation framework (#162)
Browse files Browse the repository at this point in the history
* Migrated from navigating with parameters to  type-safe navigation
* Completely migrated to type safe navigation
Handled multiple times screen navigation on tap of Home from drawer
* Removed Any from NavGraph and implemented type safety and static type checking at compile time.
* Removed Gradle migration to type-safe navigation comment line.
*Added back Header Comment in NormalScreens file.

---------

Signed-off-by: imrannextgeni021 <[email protected]>
  • Loading branch information
muhammadimran021 authored Oct 15, 2024
1 parent 7e2fab5 commit 7f662bc
Show file tree
Hide file tree
Showing 16 changed files with 149 additions and 173 deletions.
3 changes: 1 addition & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,7 @@ dependencies {
implementation 'androidx.activity:activity-compose:1.9.2'
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.6"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6"
// TODO: Needs migration to type-safe navigation in v2.8.x
implementation "androidx.navigation:navigation-compose:2.7.7"
implementation "androidx.navigation:navigation-compose:2.8.2"
// Jetpack compose.
implementation "androidx.compose.ui:ui"
implementation "androidx.compose.ui:ui-tooling-preview"
Expand Down
13 changes: 7 additions & 6 deletions app/src/main/java/com/starry/greenstash/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ import androidx.lifecycle.viewModelScope
import com.starry.greenstash.database.goal.GoalDao
import com.starry.greenstash.other.WelcomeDataStore
import com.starry.greenstash.reminder.ReminderManager
import com.starry.greenstash.ui.navigation.NormalScreens
import com.starry.greenstash.ui.navigation.DrawerScreens
import com.starry.greenstash.ui.navigation.Screens
import com.starry.greenstash.ui.navigation.Screen
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
Expand All @@ -66,9 +67,9 @@ class MainViewModel @Inject constructor(
private val _isLoading: MutableState<Boolean> = mutableStateOf(true)
val isLoading: State<Boolean> = _isLoading

private val _startDestination: MutableState<String> =
mutableStateOf(Screens.WelcomeScreen.route)
val startDestination: State<String> = _startDestination
private val _startDestination: MutableState<Screen> =
mutableStateOf(NormalScreens.WelcomeScreen)
val startDestination: State<Screen> = _startDestination

companion object {
// Must be same as the one in AndroidManifest.xml
Expand All @@ -85,9 +86,9 @@ class MainViewModel @Inject constructor(
viewModelScope.launch {
welcomeDataStore.readOnBoardingState().collect { completed ->
if (completed) {
_startDestination.value = DrawerScreens.Home.route
_startDestination.value = DrawerScreens.Home
} else {
_startDestination.value = Screens.WelcomeScreen.route
_startDestination.value = NormalScreens.WelcomeScreen
}

delay(120)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,29 @@
package com.starry.greenstash.ui.navigation

import com.starry.greenstash.R
import kotlinx.serialization.Serializable

sealed class DrawerScreens(val route: String, val nameResId: Int, val iconResId: Int) {
@Serializable
sealed class DrawerScreens(val nameResId: Int, val iconResId: Int) : Screen() {

companion object {
fun getAllItems() = listOf(Home, Archive, Backups, Settings)
}

data object Home : DrawerScreens("home", R.string.drawer_home, R.drawable.ic_nav_home)
@Serializable
data object Home : DrawerScreens(R.string.drawer_home, R.drawable.ic_nav_home)

@Serializable
data object Archive :
DrawerScreens("archive", R.string.drawer_archive, R.drawable.ic_nav_archive)
DrawerScreens(R.string.drawer_archive, R.drawable.ic_nav_archive)

@Serializable
data object Backups :
DrawerScreens("backups", R.string.drawer_backup, R.drawable.ic_nav_backups)
DrawerScreens(R.string.drawer_backup, R.drawable.ic_nav_backups)

@Serializable
data object Settings :
DrawerScreens("settings", R.string.drawer_settings, R.drawable.ic_nav_settings)
DrawerScreens(R.string.drawer_settings, R.drawable.ic_nav_settings)
}


79 changes: 25 additions & 54 deletions app/src/main/java/com/starry/greenstash/ui/navigation/NavGraph.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import androidx.navigation.toRoute
import com.starry.greenstash.ui.screens.archive.composables.ArchiveScreen
import com.starry.greenstash.ui.screens.backups.composables.BackupScreen
import com.starry.greenstash.ui.screens.dwscreen.composables.DWScreen
Expand All @@ -51,26 +50,25 @@ import com.starry.greenstash.ui.screens.welcome.composables.WelcomeScreen
@Composable
fun NavGraph(
navController: NavHostController,
startDestination: String
startDestination: Screen
) {

NavHost(
navController = navController,
startDestination = startDestination,
modifier = Modifier.background(MaterialTheme.colorScheme.background)

) {
/** Welcome Screen */
composable(
route = Screens.WelcomeScreen.route,
composable<NormalScreens.WelcomeScreen>(
exitTransition = { exitTransition() },
popEnterTransition = { popEnterTransition() },
) {
WelcomeScreen(navController = navController)
}

/** Home Screen */
composable(
route = DrawerScreens.Home.route,
composable<DrawerScreens.Home>(
enterTransition = { enterTransition() },
exitTransition = { exitTransition() },
popEnterTransition = { popEnterTransition() },
Expand All @@ -80,65 +78,44 @@ fun NavGraph(
}

/** Deposit Withdraw Screen */
composable(
route = Screens.DWScreen.route,
composable<NormalScreens.DWScreen>(
enterTransition = { enterTransition() },
exitTransition = { exitTransition() },
popEnterTransition = { popEnterTransition() },
popExitTransition = { popExitTransition() },
arguments = listOf(
navArgument(DW_GOAL_ID_ARG_KEY) {
type = NavType.StringType
},
),
popExitTransition = { popExitTransition() }
) { backStackEntry ->
val goalId = backStackEntry.arguments!!.getString(DW_GOAL_ID_ARG_KEY)!!
val transactionType =
backStackEntry.arguments!!.getString(DW_TRANSACTION_TYPE_ARG_KEY)!!
val args = backStackEntry.toRoute<NormalScreens.DWScreen>()
DWScreen(
goalId = goalId,
transactionTypeName = transactionType,
goalId = args.goalId,
transactionTypeName = args.transactionType,
navController = navController
)
}

/** Goal Info Screen */
composable(
route = Screens.GoalInfoScreen.route,
composable<NormalScreens.GoalInfoScreen>(
enterTransition = { enterTransition() },
exitTransition = { exitTransition() },
popEnterTransition = { popEnterTransition() },
popExitTransition = { popExitTransition() },
arguments = listOf(
navArgument(GOAL_INFO_ARG_KEY) {
type = NavType.StringType
},
),
popExitTransition = { popExitTransition() }
) { backStackEntry ->
val goalId = backStackEntry.arguments!!.getString(GOAL_INFO_ARG_KEY)!!
GoalInfoScreen(goalId = goalId, navController = navController)
val args = backStackEntry.toRoute<NormalScreens.GoalInfoScreen>()
GoalInfoScreen(goalId = args.goalId, navController = navController)
}

/** Input Screen */
composable(
route = Screens.InputScreen.route,
composable<NormalScreens.InputScreen>(
enterTransition = { enterTransition() },
exitTransition = { exitTransition() },
popEnterTransition = { popEnterTransition() },
popExitTransition = { popExitTransition() },
arguments = listOf(navArgument(EDIT_GOAL_ARG_KEY) {
nullable = true
defaultValue = null
type = NavType.StringType
})
popExitTransition = { popExitTransition() }
) { backStackEntry ->
val editGoalId = backStackEntry.arguments!!.getString(EDIT_GOAL_ARG_KEY)
InputScreen(editGoalId = editGoalId, navController = navController)
val args = backStackEntry.toRoute<NormalScreens.InputScreen>()
InputScreen(editGoalId = args.goalId, navController = navController)
}

/** Goal Achieved Screen */
composable(
route = Screens.CongratsScreen.route,
composable<NormalScreens.CongratsScreen>(
enterTransition = { enterTransition() },
exitTransition = { exitTransition() },
popEnterTransition = { popEnterTransition() },
Expand All @@ -148,8 +125,7 @@ fun NavGraph(
}

/** Archive Screen */
composable(
route = DrawerScreens.Archive.route,
composable<DrawerScreens.Archive>(
enterTransition = { enterTransition() },
exitTransition = { exitTransition() },
popEnterTransition = { popEnterTransition() },
Expand All @@ -159,8 +135,7 @@ fun NavGraph(
}

/** Backup Screen */
composable(
route = DrawerScreens.Backups.route,
composable<DrawerScreens.Backups>(
enterTransition = { enterTransition() },
exitTransition = { exitTransition() },
popEnterTransition = { popEnterTransition() },
Expand All @@ -170,8 +145,7 @@ fun NavGraph(
}

/** Settings Screen */
composable(
route = DrawerScreens.Settings.route,
composable<DrawerScreens.Settings>(
enterTransition = { enterTransition() },
exitTransition = { exitTransition() },
popEnterTransition = { popEnterTransition() },
Expand All @@ -181,26 +155,23 @@ fun NavGraph(
}

/** Goal Ui Settings Screen */
composable(
route = Screens.GoalCardStyle.route,
composable<NormalScreens.GoalCardStyleScreen>(
enterTransition = { enterTransition() },
popExitTransition = { popExitTransition() },
) {
GoalCardStyle(navController = navController)
}

/** Open Source Licenses Screen */
composable(
route = Screens.OSLScreen.route,
composable<NormalScreens.OSLScreen>(
enterTransition = { enterTransition() },
popExitTransition = { popExitTransition() },
) {
OSLScreen(navController = navController)
}

/** About Screen */
composable(
route = Screens.AboutScreen.route,
composable<NormalScreens.AboutScreen>(
enterTransition = { enterTransition() },
popExitTransition = { popExitTransition() },
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* MIT License
*
* Copyright (c) [2022 - Present] Stɑrry Shivɑm
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/



package com.starry.greenstash.ui.navigation

import kotlinx.serialization.Serializable

sealed class NormalScreens : Screen() {

@Serializable
data class DWScreen(val goalId: String, val transactionType: String)

@Serializable
data class InputScreen(val goalId: String? = null)

@Serializable
data class GoalInfoScreen(val goalId: String)

@Serializable
data object AboutScreen

@Serializable
data object OSLScreen

@Serializable
data object GoalCardStyleScreen


// Goal Achieved Screen
@Serializable
data object CongratsScreen

// Welcome / Onboarding Screen
@Serializable
data object WelcomeScreen : NormalScreens()
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.starry.greenstash.ui.navigation


open class Screen
Loading

0 comments on commit 7f662bc

Please sign in to comment.