Blog Infos
Author
Published
Topics
, , , ,
Published
How it worked before

In the beginning there was a navigation like startActivity (Activityname::class.java) whenever we wanted to show a new screen, alternatively you would use fragments with the FragmentManager and FragmentTransactions, manage the backstack(s), remember to use the ChildFragmentManager, too, whenever you had to, remember there is an “old” FragmentManager and a SupportFragmentManager that you could mix up etc. To overcome this Google has decided to developed the navigation component, giving us navigation graphs and a NavController that has all the power of combined. In this post, we’ll explore the Navigation component’s support for Jetpack Compose and take a look.

Quick Review about Navigation in Android

Navigation helps you in understanding how your app moves across different screens in your Application.

The Navigation Component is made up of three parts:

  1. Navigation Graph: The navigation graph provides a visual representation of the app’s navigation flow, allowing developers to design and visualize the user’s journey through the app.
  2. NavHost: NavHost essentially acts as a placeholder for swapping in and out different fragments or activities as the user navigates through the app. It provides a consistent and structured way to manage app navigation, making it easier to implement and maintain complex navigation flows.
  3. NavController: NavController is a component of the Navigation Architecture Component that manages app navigation within a NavHost. It is responsible for handling navigation within an app based on the defined navigation graph.
Lets jump into Code

Before getting started, we’ll add a dependency on navigation-compose, the Navigation component’s artifact for Compose support.

 implementation ("androidx.navigation:navigation-compose:2.7.7")

Next is how we can setup with all three components with Compose

First, we create and memoize a NavController using the rememberNavController method. rememberNavController returns a NavHostController which is a subclass of NavController that offers some additional APIs that a NavHost can use. When referring to the NavControllerand using it later on, we will use it as NavController as we don’t need to know about these additional APIs ourselves; it’s just important for the NavHost.

@Composable
    fun mainContent(){
        val mainViewModel : MainViewModel = koinViewModel()
        NavigationExampleTheme {
            val navController = rememberNavController()
            NavHost(navController = navController, startDestination = Routes.LIST_SCREEN) {
                composable(Routes.LIST_SCREEN) {
                 
                }
                composable(Routes.DETAIL_SCREEN)
                }
            }
        }
    }

I have defined routes & its names in one File named Routes.kt

object Routes {
    const val LIST_SCREEN="listScreen"
    const val DETAIL_SCREEN = "detailScreen"
}

Into your MainActivity.kt file. call this mainContent function

 setContent {
       mainContent()
  }

the Navigation Component requires that you follow the Principles of Navigation and use a fixed starting destination. You should not use a composable value for the startDestination route.

Here the demo is for a composable list UI & by clicking on the any item it will show the details screen for it, to achieve this we will have to send argument to the detail screen for mapping

How arguments will work for Jetpack Compose navigation

Navigation Compose supports passing arguments between composable destinations. In order to do this, you have to add argument placeholders to your route

Here we will use a simple argument, By default, all arguments are parsed as strings. The arguments parameter of composable() accepts a list of NamedNavArguments. You can quickly create a NamedNavArgument using the navArgument method and then specify its exact type:

@Composable
    fun mainContent(){
        val mainViewModel : MainViewModel = koinViewModel()
        NavigationExampleTheme {
            val navController = rememberNavController()
            NavHost(navController = navController, startDestination = Routes.LIST_SCREEN) {
                composable(Routes.LIST_SCREEN) {
                    RecipesScreen(navigation= navController, mainViewModel)
                }
                composable(
                    Routes.DETAIL_SCREEN,arguments= listOf(navArgument("idValue"){
                   type = NavType.IntType
                })
                ) {backStackEntry->
                    RecipeDetailScreen(navController,mainViewModel, backStackEntry.arguments?.getInt(Routes.Values.IDVALUE,0))
                }
            }
        }
    }

Above example shows Integer type Argument, so we have defined type
NavType.IntType
Here we will extract arguments from the NavBackStackEntry that is available in the lambda of the composable() function.

backStackEntry.arguments?.getInt(Routes.Values.IDVALUE,0)

Our Modified version of Routes.kt

object Routes {
    const val LIST_SCREEN="listScreen"
    const val DETAIL_SCREEN = "detailScreen/{${Values.IDVALUE}}"

    fun getSecondScreenPath(idValue: Int?): String =
        // to avoid null and empty strings
        if (idValue != null) "detailScreen/$idValue" else "detailScreen/Empty"

    object Values {
        const val IDVALUE = "idValue"
    }
}

When you navigate from one screen to another, We will simply call

navigation.navigate(Routes.getSecondScreenPath("your int value"))

for Dependency Injection Koin, You can go through on my this blog!!

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Migrating to Jetpack Compose – an interop love story

Most of you are familiar with Jetpack Compose and its benefits. If you’re able to start anew and create a Compose-only app, you’re on the right track. But this talk might not be for you…
Watch Video

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer for Jetpack Compose
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engin ...
Google

Migrating to Jetpack Compose - an interop love story

Simona Milanovic
Android DevRel Engineer f ...
Google

Jobs

https://medium.com/@nimit.raja/dependency-injection-using-koin-room-retrofit-flow-coroutines-de933d5c4420?source=post_page—–ceee45658c86——————————–

for UI state updates like success, error & loading using StateFlow , Here is the ViewModel class

class MainViewModel(private val repository: Repository, application: Application): BaseViewModel(application) {


    val _uiStateReceipeList = MutableStateFlow<UiState<Receipes>>(UiState.Loading)
    val uiStateReceipeList: StateFlow<UiState<Receipes>> = _uiStateReceipeList

    val _uiStateReceipeDetail = MutableStateFlow<UiState<Receipes.Recipe>>(UiState.Loading)
    val uiStateReceipeDetail: StateFlow<UiState<Receipes.Recipe>> = _uiStateReceipeDetail

    fun getReceipesList() = viewModelScope.launch {
        repository.getReceipes(context).collect {
            when (it) {
                is UiState.Success -> {
                    _uiStateReceipeList.value = UiState.Success(it.data)
                }
                is UiState.Loading -> {
                    _uiStateReceipeList.value = UiState.Loading
                }
                is UiState.Error -> {
                    //Handle Error
                    _uiStateReceipeList.value = UiState.Error(it.message)
                }
            }
        }
    }

    fun getReceipeDetail(id:Int?) = viewModelScope.launch {
        repository.getReceipesDetail(context,id).collect {
            when (it) {
                is UiState.Success -> {
                    _uiStateReceipeDetail.value = UiState.Success(it.data)
                }
                is UiState.Loading -> {
                    _uiStateReceipeDetail.value = UiState.Loading
                }
                is UiState.Error -> {
                    //Handle Error
                    _uiStateReceipeDetail.value = UiState.Error(it.message)
                }
            }
        }
    }

}

For Dependency Injection here is the list of modules defined for Datasource, Retrofit & ViewModel

val remoteDataSourceModule= module {
    factory {  RemoteDataSource(get()) }
}

fun provideHttpClient(): OkHttpClient {
    return OkHttpClient
        .Builder()
        .readTimeout(60, TimeUnit.SECONDS)
        .connectTimeout(60, TimeUnit.SECONDS)
        .build()
}


fun provideConverterFactory(): GsonConverterFactory =
    GsonConverterFactory.create()


fun provideRetrofit(
    okHttpClient: OkHttpClient,
    gsonConverterFactory: GsonConverterFactory
): Retrofit {
    return Retrofit.Builder()
        .baseUrl(BASE_URL)
        .client(okHttpClient)
        .addConverterFactory(gsonConverterFactory)
        .build()
}

fun provideService(retrofit: Retrofit): ApiService =
    retrofit.create(ApiService::class.java)


val networkModule= module {
    single { provideHttpClient() }
    single { provideConverterFactory() }
    single { provideRetrofit(get(),get()) }
    single { provideService(get()) }
}

val repositoryModule = module {
    factory {  Repository(get()) }
}

val viewModelModule= module {
    viewModel{ MainViewModel(get(),get()) }
}

Combine all modules that we have, inject it into our Application class that we have covered on previous article!!

startKoin {
            androidContext(this@MyApplication)
            androidLogger()
            modules(networkModule, remoteDataSourceModule, repositoryModule, viewModelModule)
        }

Finally Screen UI for list of cards using LazyColumns

@Composable
fun RecipesScreen(navigation: NavController, mainViewModel: MainViewModel) {
    Scaffold(
        topBar = {
            CustomToolbarScreen(navController = navigation, title = "Home", false)
        }
    )
    { innerPadding ->
        Column(
            modifier = Modifier
                .padding(innerPadding)
                .padding(10.dp),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            //add your code
            LaunchedEffect(key1 = Unit) {
                getReceipesListAPI(mainViewModel)
            }
            val state = mainViewModel.uiStateReceipeList.collectAsState()
            when (state.value) {
                is UiState.Success -> {
                    ProgressLoader(isLoading = false)
                    (state.value as UiState.Success<Receipes>).data?.let {
                        it.recipes?.let { it1 ->
                            RecipeList(recipes = it1) { recipe ->
                                // Handle recipe click here
                                navigation.navigate(Routes.getSecondScreenPath(recipe.id))
                            }
                        }
                    }
                }

                is UiState.Loading -> {
                    ProgressLoader(isLoading = true)
                }

                is UiState.Error -> {
                    ProgressLoader(isLoading = false)
                    //Handle Error
                }
            }
        }
    }


}

@Composable
fun RecipeListCard(recipe: Receipes.Recipe, onRecipeClick: (Receipes.Recipe) -> Unit) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(8.dp)
            .clickable { onRecipeClick(recipe) },
        shape = RoundedCornerShape(10),
        elevation = CardDefaults.cardElevation(
            defaultElevation = 4.dp
        )
    ) {
        Row(
            modifier = Modifier.padding(8.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Image(
                painter = rememberAsyncImagePainter(recipe.image),
                contentDescription = null,
                modifier = Modifier
                    .size(100.dp)
                    .clip(RoundedCornerShape(10.dp)),
                contentScale = ContentScale.Crop
            )
            Column(
                modifier = Modifier
                    .padding(start = 8.dp)
                    .weight(1f)
            ) {
                Text(
                    text = recipe.name ?: "",
                    fontWeight = FontWeight.Bold,
                    fontSize = 18.sp
                )
                Text(
                    text = "Prep Time: ${recipe.prepTimeMinutes} mins",
                    fontSize = 14.sp,
                    color = Color.Black
                )
                Text(
                    text = "Cook Time: ${recipe.cookTimeMinutes} mins",
                    fontSize = 14.sp,
                    color = Color.Black
                )
                Text(
                    text = "Servings: ${recipe.servings}",
                    fontSize = 14.sp,
                    color = Color.Black
                )
            }
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RecipeList(recipes: List<Receipes.Recipe>, onRecipeClick: (Receipes.Recipe) -> Unit) {
    LazyColumn {
        items(recipes) { recipe ->
            RecipeListCard(recipe = recipe, onRecipeClick = onRecipeClick)
        }
    }
}

private fun getReceipesListAPI(mainViewModel: MainViewModel) {
    // Call the function to fetch recipes
    mainViewModel.getReceipesList()
}

That’s it, You can find complete implementation on My Github Repository, Hope this article will helpful, Thank you.

Nimit Raja LinkedIn

This article is previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu