Home > Software design >  Navigate a user when clicked on a box in compose
Navigate a user when clicked on a box in compose

Time:08-06

I am trying to figure out how I can create a navigation when clicked on a certain box. Basically when the user clicks a box, it should navigate to a composable screen. The user can then select what they want from the screen and then navigate back to the "start" screen of choices.

All my boxes are in a lazyRow. How can I acheieve this ? I have set up 1 sealed class of objects with different names. 1 Navhost with all the respective composable screens. How can I link the 2 together to create navigation for each respective Box(items) when clicked ?

Sealed class:

sealed class HobbiesItem(var route: String, var title: String) {

    object Item1 : HobbiesItem("Item1", "Item1")
    object Item2 : HobbiesItem("Item2", "Item2")
    object Item3 : HobbiesItem("Item3", "Item3")
    object Item4 : HobbiesItem("Item4", "Item4")
    object Item5 : HobbiesItem("Item5", "Item5")
    object Item6 : HobbiesItem("Item6", "Item6")
    object Item7 : HobbiesItem("Item7", "Item7")

}

NavHost:

@Composable
fun Navigation2(navController2: NavHostController) {

    NavHost(navController2, startDestination = HobbiesItem.Item1.route) {

        composable(HobbiesItem.Item1.route) {
            Hobbies01()
        }

        composable(HobbiesItem.Item2.route) {
            Hobbies02()
        }

        composable(HobbiesItem.Item3.route) {
            Hobbies03()
        }

        composable(HobbiesItem.Item4.route) {
            Hobbies04()
        }

        composable(HobbiesItem.Item5.route) {
            Hobbies05()
        }

        composable(HobbiesItem.Item6.route) {
            Hobbies06()
        }

        composable(HobbiesItem.Item7.route) {
            Hobbies07()
        }

    }

}

My LazyRow with the Boxes:

How it looks in Emulator: https://gyazo.com/cc62241a5ceb4769eeaebd42d241d27b

// Displays all the items in a lazyRow
@OptIn(ExperimentalSnapperApi::class)
@Composable
fun Trying(HobbyRowItems: HobbiesItem, onItemClick: (HobbiesItem) -> Unit) {

    val allStackHobbies = listOf(
        HobbiesItem.Item1.route,
        HobbiesItem.Item4.route,
        HobbiesItem.Item7.route,
        HobbiesItem.Item2.route,
        HobbiesItem.Item3.route,
        HobbiesItem.Item5.route,
        HobbiesItem.Item6.route
        )

    val allItems = remember { (allStackHobbies).map { it } }
    val horizontalContentPadding = 3.dp // Moves the content left or right of the LazyRow
    val boxSize = 115.dp
    val lazyListState10 = rememberLazyListState()

    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {

        BoxWithConstraints {
            val halfRowWidth = constraints.maxWidth / 2


            LazyRow(
                state = lazyListState10,
                flingBehavior = rememberSnapperFlingBehavior(lazyListState10),
                horizontalArrangement = Arrangement.spacedBy(16.dp),
                contentPadding = PaddingValues(horizontal = horizontalContentPadding, vertical = 8.dp),
                modifier = Modifier
                    .fillMaxWidth()
            ) {
                itemsIndexed(allItems) { i, item ->
                    val opacity by remember { derivedStateOf {
                        val currentItemInfo = lazyListState10.layoutInfo.visibleItemsInfo
                            .firstOrNull { it.index == i }
                            ?: return@derivedStateOf 0.5f
                        val itemHalfSize = currentItemInfo.size / 2
                        (1f - minOf(1f, abs(currentItemInfo.offset   itemHalfSize - halfRowWidth).toFloat() / halfRowWidth) * 0.5f)
                    }
                    }
                    Box(
                        contentAlignment = Alignment.Center,
                        modifier = Modifier
                            .scale(opacity)
                            .alpha(opacity)
                            .size(boxSize)
                            .background(Color.Blue)
                            .clickable {
                                onItemClick.invoke(HobbyRowItems)
                                Log.d("Hobby", "Pressed Box")

                            } 
                    ) {
                        Text(item, color = Color.White)

                    }
                }
            }
        }
    }

}

CodePudding user response:

I have set up 1 sealed class of objects with different names. 1 Navhost with all the respective composable screens. How can I link the 2 together to create navigation for each respective Box(items) when clicked ?

You are running into a problem because your allItems list contains only routes (strings) but your onItemClick callback wants the whole HobbiesItem.

What you can do here is instead of passing just a list of routes to your LazyRow, you can pass a list of all HobbiesItems. You could also hoist the items outside the Trying composable, which would make your composable stateless and would let you control the state outside of it.

// Displays all the items in a lazyRow
@OptIn(ExperimentalSnapperApi::class)
@Composable
// change the function signature
fun Trying(items: List<HobbiesItem>, onItemClick: (HobbiesItem) -> Unit) {

    // get rid of these
    // val allStackHobbies = listOf(...)
    // val allItems = remember { (allStackHobbies).map { it } }
    // `remember` is not needed anymore because `items` are now passed
    // into this composable as a parameter

    // ...

            LazyRow(
                // ...
            ) {
                itemsIndexed(items) { i, item -> // <-- change here to `items`
                    // ...
                    Box(
                        contentAlignment = Alignment.Center,
                        modifier = Modifier
                            // ...
                            .clickable {   
                                onItemClick(item) // <-- change here to `item`
                                Log.d("Hobby", "Pressed Box")

                            } 
                    ) {
                        // you probably wanted to show the title here instead of the route,
                        // even though in your example both are the same?
                        Text(item.title, color = Color.White) // <-- change here to `item.title`
                    }
               }
           }
    // ...
}

Then, in your top composable (usually the composable in your main/landing Activity), where (I assume) you are currently displaying your Trying composable, you have to display the NavHost composable instead. In your case that would be your Navigation2 composable.

So something like this (here I am assuming you are using the ComponentActivity, but is similar in other scenarios)

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            // if you already have a reference to the navController2
            // from elsewhere then remove the next line
            val navController2 = rememberNavController()
    
            MyTheme { // this would be the name of your theme
                // you can wrap this into a Scaffold in the future
                Navigation2(navController2)
            }
        }
    }
}

And finally you connect them together where you will be displaying your Trying composable, which I assume it would be your actual staring destination.

So make a new composable desination that calls your Trying composable and change the starting destination so that it uses the same route.

@Composable
fun Navigation2(navController2: NavHostController) {
    NavHost(navController2, startDestination = "ItemList") {
        composable("ItemList") {
            val allItems = listOf(
                HobbiesItem.Item1, HobbiesItem.Item4, HobbiesItem.Item7, HobbiesItem.Item2,
                HobbiesItem.Item3, HobbiesItem.Item5, HobbiesItem.Item6
            )
            Trying(
                items = allItems,
                // navigating on an item click
                onItemClick = { item -> navController2.navigate(item.route) }
            )
        }

        composable(HobbiesItem.Item1.route) {
            Hobbies01()
        }

        composable(HobbiesItem.Item2.route) {
            Hobbies02()
        }

        // ...
    }
}
  • Related