Search code examples
androidkotlinandroid-jetpack-composeandroid-jetpack-navigation

How to navigate to Detail View clicking in LazyColumn item with JetPack Compose?


I am trying to create an app with JetPack Compose (first time) and I am having some problem with the navigation. The application has a bottomBar with 3 items that navigates to the selected screen.

This works, the problem is when I try to access one of the items of a LazyColumn that is in one of the screens. I would like to navigate to another screen (Profile) where the data of the selected item is displayed but I can't find a way to do it. No matter how I try to do it, I always get this "@Composable invocations can only happen from the context of a @Composable function".

Could someone help me by explaining how to do it? What I want is to learn how and why, not just copy.

Thanks

MainActivity

class MainActivity : ComponentActivity() {

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


        setContent {
            val systemUiController = rememberSystemUiController()

            SideEffect {
                systemUiController.setStatusBarColor(color = PrimaryDark)
            }
            AppTheme() {


                MainScreen()

            }

        }
    }

    @ExperimentalFoundationApi
    @Composable
    fun MainScreen() {
        val navController = rememberNavController()
        val navigationItems = listOf(Obras, Talleres, Ajustes)
        Scaffold(bottomBar = {
            BottomNavigationBar(
                navController = navController,
                items = navigationItems
            )
        }) {

            NavigationHost(navController)
        }
    }
} 

NavigationHost.kt

@ExperimentalFoundationApi
@Composable
fun NavigationHost(navController: NavHostController) {


    NavHost(navController = navController, startDestination = Obras.route) {
        composable(Obras.route) {
            Pantalla1(navigateToProfile = { authorId ->
                navController.navigate("${Profile.route}/$authorId")
            })

        }
        composable(Talleres.route) {
            Pantalla2()
        }

        composable(Ajustes.route) {
            Pantalla3()
        }

        composable(
            Profile.route + "/{authorId}",
            arguments = listOf(navArgument("authorId") { type = NavType.StringType })
        ) { backStackEntry ->
            val authorId = backStackEntry.arguments!!.getString("authorId")!!
            Profile(authorId)
        }

    }
}

Pantalla1.kt

typealias AuthorId = String

@ExperimentalCoroutinesApi
@Composable
fun Pantalla1(navigateToProfile: (AuthorId) -> Unit) {


    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(
                paddingValues = PaddingValues(

                    bottom = 50.dp
                )
            ),
    ) {

        AutoresInfo(navigateToProfile)
    }

}

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun AutoresInfo(navigateToProfile: (AuthorId) -> Unit) {
    var autoresList by remember {
        mutableStateOf<List<Autor>?>(null)
    }

    JetFirestore(path = { collection("autores") },
        queryOnCollection = { orderBy("nombre", Query.Direction.ASCENDING) },

        onRealtimeCollectionFetch = { value, exception ->
            autoresList = value.getListOfObjects()
        }) {
        autoresList?.let {
            val grouped = it.groupBy { it.nombre[0] }
            LazyColumn(

                modifier = Modifier.fillMaxSize()

            ) {


                grouped.forEach { (initial, autoresForInitial) ->
                    stickyHeader {
                        StickyHeaderAutores(initial = initial.toString())
                    }

                    items(autoresForInitial, key = { autor -> autor.nombre }) { autor ->
                        Surface(modifier = Modifier.clickable { navigateToProfile(autor.nombre) }) {
                            @OptIn(coil.annotation.ExperimentalCoilApi::class)
                            AutorCard(autor)
                        }


                    }
                }


            }
        } ?: Column(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            CircularProgressIndicator(
                color = Color.Red,
                modifier = Modifier

                    .size(50.dp)

            )
        }

    }


}


Solution

  • Step 1. Add argument to your profile navigation route. Check out documentation about navigating with arguments

    composable(
        Profile.route + "/{authorId}",
        arguments = listOf(navArgument("authorId") { type = NavType.StringType })
    ) { backStackEntry ->
        val authorId = backStackEntry.arguments!!.getString("authorId")!!
        Profile(authorId)
    }
    

    Step 2. You need to pass down navigateToProfile function from your NavigationHost. You can replace AuthorId with Int(or an other type) if it's not String:

    typealias AuthorId = String
    
    @Composable
    fun NavigationHost(navController: NavHostController){
        NavHost(navController = navController, startDestination = Obras.route) {
            composable(Obras.route) {
                Pantalla1(navigateToProfile = { authorId ->
                    navController.navigate("${Profile.route}/$authorId")
                })
            }
            ... 
    }
    
    @Composable
    fun Pantalla1(navigateToProfile: (AuthorId) -> Unit) {
        ...
            AutoresInfo(navigateToProfile)
        ...
    }
    
    @Composable
    fun AutoresInfo(navigateToProfile: (AuthorId) -> Unit) {
        ...
        items(autoresForInitial, key = { autor -> autor.nombre }) { autor ->
            Surface(modifier = Modifier.clickable {
                navigateToProfile(author.id)
            }) {
                AutorCard(autor)
            }
        }
        ...
    }
    

    Step 3. In you profile composable you need to fetch author by id. Not sure what's JetFirestore, you probably should use it.

    @Composable
    fun Profile(id: AuthorId) {
        JetFirestore(
            // fetch author by id
        )
    }