Hi guys, I was learning `ModalBottomSheetLayout` i...
# compose
k
Hi guys, I was learning
ModalBottomSheetLayout
in my project. I have a
Button
and
onClick
I am sending
Boolean
to show
sheetContent
. It works once without any problem. After clicking on outside of
ModalBottomSheetLayout
it closes the
sheetContent
. After again clicking on
Button
, sheet is not opening again. I don't understand what is the problem in here.
MainActivity.kt
Copy code
class MainActivity : AppCompatActivity() {

    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    private val viewModel by viewModels<MainActivityViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        viewModel.enableBottomSheet.observe(this) {
            binding.bottomSheetComposeView.setContent {
                ModalBottomSheetSample(it)
            }
        }

        binding.buttonComposeView.setContent {
            ButtonView()
        }
    }

    @Composable
    fun ButtonView() {
        Column(Modifier.fillMaxSize()) {
            Button(onClick = {
                viewModel.enableBottomSheet.postValue(true)
            }) {
                Text(text = "Open Bottom Sheet")
            }
        }
    }

    @Composable
    @OptIn(ExperimentalMaterialApi::class)
    fun ModalBottomSheetSample(value: Boolean) {
        val state = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden, skipHalfExpanded = true)
        val scope = rememberCoroutineScope()
        SideEffect {
            scope.launch {
                if (value) {
                    state.show()
                }
            }
        }
        ModalBottomSheetLayout(
            sheetState = state,
            sheetContent = {
                LazyColumn {
                    items(5) {
                        ListItem(
                            icon = {
                                Icon(
                                    Icons.Default.Favorite,
                                    contentDescription = null
                                )
                            },
                            text = { Text("Item $it") },
                        )
                    }
                }
            }
        ) {}
    }

}
MainActivityViewModel.kt
Copy code
class MainActivityViewModel : ViewModel() {
    val enableBottomSheet by lazy { MutableLiveData(false) }
}
I am using
compose_bom = "2023.03.00"
c
There are couple of flaws in your code - biggest one is mixing state and events when handling the “view state” of the bottom sheet. You should revise the whole flow and come up with a better approach. Hint: you are not updating your live data when the sheet is closed.
k
Thanks for reply Christian. Sorry I didn't get the
state and events when handling the "view state" of the bottom sheet
. Is there example for this?
c
basically the compose state documentation https://developer.android.com/jetpack/compose/state
The pattern where the state goes down, and events go up is called a unidirectional data flow. In this case, the state goes down from
HelloScreen
to
HelloContent
and events go up from
HelloContent
to
HelloScreen
. By following unidirectional data flow, you can decouple composables that display state in the UI from the parts of your app that store and change state.
so your
ModalBottomSheetSample
has a state of “showing” but does not react on the “event” of when it’s hidden.
k
Okk got it.. It looks like this way ?
Copy code
@Composable
@OptIn(ExperimentalMaterialApi::class)
fun ModalBottomSheetSample(value: Boolean) {
    val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden, skipHalfExpanded = true)
    val scope = rememberCoroutineScope()
    SideEffect {
        scope.launch {
            if (value) {
                sheetState.show()
            }
        }
    }
    ModalBottomSheetContent(sheetState)
}

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ModalBottomSheetContent(sheetState: ModalBottomSheetState) {
    ModalBottomSheetLayout(
        sheetState = sheetState,
        sheetContent = {
            LazyColumn {
                items(5) {
                    ListItem(
                        icon = {
                            Icon(
                                Icons.Default.Favorite,
                                contentDescription = null
                            )
                        },
                        text = { Text("Item $it") },
                    )
                }
            }
        }
    ) {}
}
c
you still don’t update the live data to
false
when the sheet is closed. I’d remove the code and rethink the whole approach. like you are doing it now, it will not work.
k
Yeah good point for that. I am thinking when we close the
ModalBottomSheetContent
, then I'll update the livedata value. How can we know that
ModalBottomSheetContent
is closed?
c
thats for you to find out - welcome to the world of programming and problem solving 😉 👍
k
Thanks for the guidance. No worries I can try something different. I change my
LiveData
to
MutableSharedFlow
. I am sending every time
true
when click on the`Button`.
Copy code
class MainActivityViewModel : ViewModel() {
    val showBottomSheetContent by lazy { MutableSharedFlow<Boolean>() }
}
MainActivity.kt
Copy code
class MainActivity : AppCompatActivity(), HideViewListener {

    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    private val viewModel by viewModels<MainActivityViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.showBottomSheetContent
                    .collect { value ->
                        binding.bottomSheetComposeView.setContent {
                            ModalBottomSheetSample(value)
                        }
                    }
            }
        }

        binding.buttonComposeView.setContent {
            ButtonViewContent()
        }
    }

    @Composable
    fun ButtonViewContent() {
        val scope = rememberCoroutineScope()
        Column(Modifier.fillMaxSize()) {
            Button(
                onClick = {
                    scope.launch {
                        viewModel.showBottomSheetContent.emit(true)
                    }
                }
            ) {
                Text(text = "Open Bottom Sheet")
            }
        }
    }

    @Composable
    @OptIn(ExperimentalMaterialApi::class)
    fun ModalBottomSheetSample(value: Boolean) {
        val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden, skipHalfExpanded = true)
        val scope = rememberCoroutineScope()
        SideEffect {
            scope.launch {
                if (value) {
                    sheetState.show()
                }
            }
        }
        ModalBottomSheetContent(sheetState)
    }

    @OptIn(ExperimentalMaterialApi::class)
    @Composable
    fun ModalBottomSheetContent(sheetState: ModalBottomSheetState) {
        ModalBottomSheetLayout(
            sheetState = sheetState,
            sheetContent = {
                LazyColumn {
                    items(5) {
                        ListItem(
                            icon = {
                                Icon(
                                    Icons.Default.Favorite,
                                    contentDescription = null
                                )
                            },
                            text = { Text("Item $it") },
                        )
                    }
                }
            }
        ) {}
    }

}
Now it working fine..
Now every time
BottomSheet
open and close, without any problem. Is there any thing need to improve?
c
yes
Copy code
lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.showBottomSheetContent
                    .collect { value ->
                        binding.bottomSheetComposeView.setContent {
                            ModalBottomSheetSample(value)
                        }
                    }
            }
        }
why do you need that? the bottom sheet is handled by its state and not the value of the shared flow.
k
Good point. I change the function not to use value anymore.
Copy code
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.showBottomSheetContent
            .collect {
                binding.bottomSheetComposeView.setContent {
                    ModalBottomSheetSample()
                }
            }
    }
}
ModalBottomSheetSample
Copy code
@Composable
@OptIn(ExperimentalMaterialApi::class)
fun ModalBottomSheetSample() {
    val sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden, skipHalfExpanded = true)
    val scope = rememberCoroutineScope()
    SideEffect {
        scope.launch {
            sheetState.show()
        }
    }
    ModalBottomSheetContent(sheetState)
}
385 Views