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

Issue

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
    )
}

Answered By – Philip Dukhov

Leave a Comment