Home > OS >  In this android-compose codelab why do they pass Lambda instead of navController?
In this android-compose codelab why do they pass Lambda instead of navController?

Time:10-29

Why does this codelab at android developers passes a "onNextButtonClicked: () -> Unit," instead of just passing the "navController: NavController,"... it seems simpler.. is there a reason for this?

enter image description here enter image description here

Codelab in github

CodePudding user response:

This is called state hoisting. You could read more on official documentation.

A quick answer to your question though: The way you mentioned it will work. But by hoisting state — that is, to move a composable’s state outside of the composable and push it further up, by making the composable stateless it results in components that are easier to reuse and test!

CodePudding user response:

Passing the nav controller to screens is not a good practice, one of the previous answers.

Test writing: When you reach the command you want through callback to the highest level; You are now comfortable for writing tests and you don't need to mock the new controller.

Therefore, only by checking the callbacks that you have used, you can ensure the correctness of the functionality of your composables.

Consider the following example:

PostScreen


import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember

@Composable
fun PostScreen(
    navigateToPostDetail: (PostId: PostId) -> Unit
) {
    val posts = remember {
        mutableListOf<Post>().apply {
            repeat(10) {
                add(
                    Post(
                        id = "$it", title = "Title $it"
                    )
                )
            }
        }
    }
    PostScreenContent(
        posts = posts,
        navigateToPostDetail = navigateToPostDetail
    )
}

data class Post(
    val id: String, val title: String
)

@Composable
internal fun PostScreenContent(
    posts: List<Post>,
    navigateToPostDetail: (PostId: PostId) -> Unit
) {
    LazyColumn {
        items(items = posts, key = { item -> item.id }) { item ->
            Text(text = item.title)
            Button(onClick = {
                navigateToPostDetail(PostId(item.id))
            }
            ) {
                Text(text = "Navigate To Detail")
            }
        }
    }

}

@JvmInline
value class PostId(val value: String)

PostScreenTest


import androidx.activity.ComponentActivity
import androidx.compose.runtime.remember
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import org.junit.Rule
import org.junit.Test


internal class PostScreenTest {
    @get:Rule
    val composeTestRule = createAndroidComposeRule<ComponentActivity>()

    @Test
    fun clickOnPostItem() {
        var expectedPostId = "your expected id"
        var actualPostId: String
        with(composeTestRule) {
            setContent {
                val posts = remember {
                    mutableListOf<Post>().apply {
                        repeat(10) {
                            add(
                                Post(
                                    id = "$it",
                                    title = "Title $it"
                                )
                            )
                        }
                    }
                }
                PostScreenContent(
                    posts = posts,
                    navigateToPostDetail = {
                        actualPostId = it.value
                    }
                )
            }
            assertThat(actualPostId).isEqualTo(expectedPostId)
        }
    }
}

However, if you want to test the navigation logic of the whole app, it is enough to test your highest layer, the place where you used NavHost.

  • Related