I have two composables like this:
#Composable
fun Composable1(viewModel: MyViewModel) {
LaunchedEffect(Unit) {
viewModel.eventsFlow.collect { event ->
if(event is ShowSnackbar) {
// Send this event to Composable2 to show snackbar
}
}
}
Composable2(...) // passing some data and lambdas
}
#Composable
fun Composable2(...) {
val scaffoldState = rememberScaffoldState()
// On receiving event, show a snackbar
Scaffold(scaffoldState) {
// Other stuff
}
}
(If another ShowSnackbar event comes while one snackbar is visible, I want to ignore that new event)
How to send such an event from one composable to another?
I created a small example. I hope It is help you. In my case I generate a "event" by means of clicking a button
class ComposeActivity5 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeTutorialTheme {
Composable1()
}
}
}
}
#Composable
fun Composable1() {
val scaffoldState = rememberScaffoldState()
var showHide by remember { mutableStateOf(false) }
var pressCount by remember { mutableStateOf(0) }
Scaffold(
scaffoldState = scaffoldState,
content = { innerPadding ->
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Button(onClick = {
pressCount++
showHide = true
}) {
Text(text = "Test")
}
Composable2(scaffoldState, showHide, pressCount) {
showHide = false
}
}
}
)
}
#Composable
fun Composable2(
scaffoldState: ScaffoldState,
showHide: Boolean,
pressCount: Int,
onDismiss: () -> Unit
) {
val mostRecentOnDismiss by rememberUpdatedState(onDismiss)
LaunchedEffect(scaffoldState, showHide) {
if (showHide) {
scaffoldState.snackbarHostState.showSnackbar(
message = "We are ignore press button to show Snackbar. Total number of clicks $pressCount",
actionLabel = "Close",
duration = SnackbarDuration.Short,
)
mostRecentOnDismiss()
}
}
}
Related
I'm trying to implement https://google.github.io/accompanist/navigation-material/ and i want to expand modelsheet to custom height or more than half screen but i don't have any idea how to achieve it
Currently ModelBottomSheet
Wish to expand like this
Instead of using the show method of the ModalBottomSheetState, You can use the animateTo method. The show method will default to a half screen size modal. The animateTo(ModalBottomSheetValue.Expanded) will expand to the full size of the content. In the example i've used a BoxWithConstrains to get the screen size and set the size of the modal content to 80%.
I hope this helps!
#Composable
#Preview
fun BottomSheetDemo() {
val modalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
BoxWithConstraints {
val sheetHeight = this.constraints.maxHeight * 0.8f
val coroutineScope = rememberCoroutineScope()
Column {
Button(onClick = {
coroutineScope.launch { modalBottomSheetState.animateTo(ModalBottomSheetValue.Expanded) }
}) {
Text(text = "Expand")
}
Button(onClick = {
coroutineScope.launch { modalBottomSheetState.animateTo(ModalBottomSheetValue.Hidden) }
}) {
Text(text = "Collapse")
}
}
ModalBottomSheetLayout(
sheetBackgroundColor = Color.Red,
sheetState = modalBottomSheetState,
sheetContent = {
Box(modifier = Modifier.height(with(LocalDensity.current) { sheetHeight.toDp() })) {
Text(text = "This is some content")
}
}
) {}
}
}
EDIT:
If you want to use the material navigation, you will need a custom extension function. The difference in this function with the original is the skipHalfExpanded parameter. This on will make it possible to create bottom sheets larger then half screen.
#Composable
fun rememberBottomSheetNavigator(
animationSpec: AnimationSpec<Float> = SwipeableDefaults.AnimationSpec
): BottomSheetNavigator {
val sheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
animationSpec = animationSpec,
skipHalfExpanded = true
)
return remember(sheetState) {
BottomSheetNavigator(sheetState = sheetState)
}
}
The implementation itself will be something like this:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val bottomSheetNavigator = rememberBottomSheetNavigator()
val navController = rememberNavController(bottomSheetNavigator)
ModalBottomSheetLayout(bottomSheetNavigator) {
NavHost(navController, "home") {
composable(route = "home") {
Home(navController)
}
bottomSheet(route = "sheet") {
ModalDemo()
}
}
}
}
}
}
#Composable
fun Home(navController: NavController) {
val coroutineScope = rememberCoroutineScope()
Column {
Button(onClick = {
coroutineScope.launch { navController.navigate("sheet") }
}) {
Text(text = "Expand")
}
}
}
#Composable
fun ModalDemo() {
Column(Modifier.fillMaxWidth().height(700.dp).background(Color.Red), horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "This is some content")
}
}
I am using MutableStateFlow UI State in jetpack compose. I am not getting proper flow of my UI event. In UI state there is Empty, Loading, Success and Error state. I setup a Empty state when I initialise a variable. When I am starting to call api before that I am trigger Loading state. On that basis I am triggering Success or Error event.
Note: I am not adding imports and package name. If you want to see full code please click a name of class you will redirect to my repository.
MainActivityViewModel.kt
class MainActivityViewModel(private val resultRepository: ResultRepository) : ViewModel() {
val stateResultFetchState = MutableStateFlow<ResultFetchState>(ResultFetchState.OnEmpty)
fun getSportResult() {
viewModelScope.launch {
stateResultFetchState.value = ResultFetchState.IsLoading
val result = resultRepository.getSportResult()
delay(5000)
result.handleResult(
onSuccess = { response ->
if (response != null) {
stateResultFetchState.value = ResultFetchState.OnSuccess(response)
} else {
stateResultFetchState.value = ResultFetchState.OnEmpty
}
},
onError = {
stateResultFetchState.value =
ResultFetchState.OnError(it.errorResponse?.errorMessage)
}
)
}
}
}
MainActivity.kt
internal lateinit var networkConnection: NetworkConnection
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
networkConnection = NetworkConnection(application)
setContent {
SportsResultTheme {
SetupConnectionView()
}
}
}
}
#Composable
fun SetupConnectionView() {
val isConnected = networkConnection.observeAsState()
if (isConnected.value == true) {
NavigationGraph()
} else {
NoInternetView()
}
}
#Composable
fun NoInternetView() {
Box(
modifier = Modifier
.fillMaxSize()
.background(getBackgroundColor()),
contentAlignment = Center,
) {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.nointernet))
LottieAnimation(
composition,
iterations = LottieConstants.IterateForever
)
}
}
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun SetupMainActivityView(
viewModel: MainActivityViewModel = koinViewModel(),
navigateToNext: (state: String) -> Unit,
) {
Scaffold(topBar = {
TopAppBar(
title = { Text(text = stringResource(id = R.string.app_name)) },
backgroundColor = getBackgroundColor(),
elevation = 0.dp
)
}, content = { padding ->
Column(
modifier = Modifier
.fillMaxSize()
.background(getBackgroundColor())
.padding(padding),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = {
viewModel.getSportResult()
}) {
Text(text = stringResource(id = R.string.get_result))
}
}
})
when (val state = viewModel.stateResultFetchState.collectAsState().value) {
is ResultFetchState.OnSuccess -> {
navigateToNext("loading $state")
}
is ResultFetchState.IsLoading -> {
LoadingFunction()
}
is ResultFetchState.OnError -> {}
is ResultFetchState.OnEmpty -> {}
}
}
#Composable
fun LoadingFunction() {
Column(
modifier = Modifier
.fillMaxSize()
.background(getBackgroundColor()),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator()
}
}
I am adding my navigation graph so you will clearly see what I am trying to do.
NavigationGraph.kt
#Composable
internal fun NavigationGraph() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = ScreenRoute.Home.route) {
composable(ScreenRoute.Home.route) {
SetupMainActivityView { state ->
navController.navigate(ScreenRoute.Result.route + "/{$state}")
}
}
composable(
ScreenRoute.Result.route + "/{state}",
arguments = listOf(
navArgument("state") { type = NavType.StringType }
)
) { backStackEntry ->
ResultScreen(backStackEntry.arguments?.getString("state").orEmpty())
}
}
}
ResultScreen.kt
#Composable
fun ResultScreen(state: String) {
Log.e("TAG", "ResultScreen: $state" )
}
Actual Output
when you click on Button it started Loading screen. After Loading screen my Button screen appears than my Result Screen appears. You can see in my video.
Button Screen -> Loading Screen -> Again Button Screen -> Result Screen.
Expected Output
Button Screen -> Loading Screen -> Result Screen.
My Github project link. Can you guys guide me what I am doing wrong here. Many Thanks
UPDATE
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun SetupMainActivityView(
viewModel: MainActivityViewModel = koinViewModel(),
navigateToNext: (nearestResult: ArrayList<NearestResult>) -> Unit,
) {
Scaffold(topBar = {
TopAppBar(
title = { Text(text = stringResource(id = R.string.app_name)) },
backgroundColor = getBackgroundColor(),
elevation = 0.dp
)
}, content = { padding ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.background(getBackgroundColor()),
contentAlignment = Center
) {
when (val state = viewModel.stateResultFetchState.collectAsState().value) {
is ResultFetchState.OnSuccess -> {
LaunchedEffect(Unit) {
navigateToNext(state.nearestResult)
}
}
is ResultFetchState.IsLoading -> {
LoadingFunction()
}
is ResultFetchState.OnError,
is ResultFetchState.OnEmpty -> {
ActivityContent(viewModel)
}
}
}
})
}
After doing this my ResultScreen calling twice. Is it normal?
During loading you overlap the button view with the loading view, but when you succeed you remove the loading view, so the button view appears for the transition.
Depending on the expected behavior, you can move your when inside the content, and display content only on empty/error - it might make sense to leave the option to click back to cancel the request.
content = { padding ->
Box(Modifier.fillMaxSize().padding(padding).background(getBackgroundColor())) {
when (val state = viewModel.stateResultFetchState.collectAsState().value) {
is ResultFetchState.OnSuccess -> {
LaunchedEffect(Unit){
navigateToNext("loading $state")
}
}
is ResultFetchState.IsLoading -> {
LoadingFunction()
}
is ResultFetchState.OnError, is ResultFetchState.OnEmpty -> {
YourContent()
}
}
}
})
Or add LoadingFunction() inside ResultFetchState.OnSuccess, so that this view doesn't disappear from the screen during the transition.
is ResultFetchState.OnSuccess -> {
LaunchedEffect(Unit){
navigateToNext("loading $state")
}
LoadingFunction()
}
Also see this answer for why calling navigateToNext as you do is unsafe and why I've added LaunchedEffect.
My app contains 2 different layouts for different device types. I want different actions to be performed e.g. 1st, 2nd and 3rd items to navigate to different screens, 4th item to show a Toast, the 5th item to launch an email composer intent, etc. Is there way to find out which index of the array was clicked and making these click events reusable rather than creating the same click event twice?
array in strings.xml
<string-array name="array_main">
<item>#string/breads</item>
<item>#string/cakes</item>
<item>#string/condiments</item>
<item>#string/desserts</item>
<item>#string/snacks</item>
<item>#string/contact us</item>
</string-array>
MainActivity.kt
class ActivityMain : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyAppTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
HomeScreen(navController = rememberNavController())
}
}
}
}
#Composable
fun ComposeNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home_screen"
) {
composable("home_screen") {
HomeScreen(navController = navController)
}
composable("second_screen") {
SecondScreen(navController = navController)
}
composable("third_screen") {
ThirdScreen(navController = navController)
}
}
}
#Composable
fun HomeScreen(navController: NavController) {
val windowInfo = rememberWindowInfo()
if (windowInfo.screenWidthInfo is WindowInfo.WindowType.Compact) {
Scaffold(
topBar = {...},
content = {
MyLazyColumn(lazyItems = resources.getStringArray(R.array.array_main))
},
)
} else {
Scaffold(
topBar = {...},
content = {
MyLazyVerticalGrid(lazyItems = resources.getStringArray(R.array.array_main))
},
)
}
}
}
MyLazyColumn.kt
#Composable
fun MyLazyColumn(lazyItems: Array<String>) {
val listState = rememberLazyListState()
LazyColumn(
state = listState,
) {
items(lazyItems) { item ->
Row(modifier = Modifier
.fillMaxWidth()
.clickable {
}) {
Text(
text = item,
)
}
}
}
}
MyLazyVerticalGrid.kt
fun MyLazyVerticalGrid(lazyItems: Array<String>) {
val lazyGridState = rememberLazyGridState()
LazyVerticalGrid(
state = lazyGridState,
columns = GridCells.Fixed(2),
content = {
items(lazyItems.size) { index ->
OutlinedButton(
modifier = Modifier.padding(7.dp),
onClick = {/*TODO*/ }) {
Text(text = lazyItems[index]
)
}
}
}
)
}
Since we're using Compose, we can take advantage of Kotlin.
Instead of creating a string-array resource, we can create an enum class with all the choices like this:
enum class Choices(#StringRes val textResId: Int) {
Breads(R.string.breads),
Cakes(R.string.cakes),
Condiments(R.string.condiments),
Desserts(R.string.desserts),
Snacks(R.string.snacks),
ContactUs(R.string.contact_us),
}
Then, we can setup your LazyColumn and LazyGrid like this:
#Composable
fun MyLazyColumn(
lazyItems : Array<Choice>,
onClickItem: (Choice) -> Unit
) {
//...
items(lazyItems) { choice ->
//...
Text(text = stringResource(choice.textResId))
Button(
//...
onClick = { onClickItem(choice) }
)
//...
}
//...
}
#Composable
fun MyLazyGrid(
lazyItems : Array<Choice>,
onClickItem: (Choice) -> Unit
) {
// ...
items(lazyItems) { choice ->
Text(text = stringResource(choice.textResId))
Button(
onClick = { onClickItem(choice) }
)
}
// ...
}
To reuse the ClickListener, we can create a lambda object and reuse it:
#Composable
fun HomeScreen(/* ... */) {
val listener: (Choice) -> Unit = { choice ->
when(choice) {
Breads -> {}
Cakes -> {}
Condiments -> {}
Desserts -> {}
Snacks -> {}
ContactUs -> {}
}
}
MyLazyGrid(
lazyItems = arrayOf(
/* Choice items you'd add to you list */
),
onClickItem = listener
)
MyLazyColumn(
lazyItems = arrayOf(
/* Choice items you'd add to you list */
),
onClickItem = listener
)
}
Just store your click-codeblocks as variables.
val firstItemClicker = { /* Add Actions Here */ }
val secondItemClicker = { /* and So on */ }
Use it like
FirstItem(
Modifier.clickable { firstItemClicker() }
)
Or,
Button(
onclick = { firstItemClicker() }
){
Text("Click Recyler")
}
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)
)
}
}
}
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
)
}
create a sealed class that contains the route like below
sealed class Screen(val route:String){
object MyListScreen:Screen("my_list_screen")
object MyDetailScreen:Screen("my_detail_screen")
}
create listItem with Row that receive a list and an event of click like below
#Composable
fun MyListItem(
yourList: YourList,
onItemClick:(yourList) -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
onItemClick(yourList)
}
){
//something here
}
Inside your listScreen in LazyColoumn you can do this
LazyColumn(modifier = Modifier.fillMaxSize()){
items (state.yourfreshlist){mylist->
MyListItem(mylist = mylist, onItemClick = {
navController.navigate(Screen.MyDetailScreen.route+"/${mylist.something}")
}
)
}
}
your host will be like this
NavHost(
navController = navController,
startDestination = Screen.MyListScreen.route
) {
composable(
route = Screen.MyListScreen.route
) {
MyListScreen(navController)
}
composable(
route = Screen.MyDetailScreen.route + "/{anything here id for example}"
) {
MyDetailScreen()
}
}
For more information, you can browse the docs
I have this composable function that a button will toggle show the text and hide it
#Composable
fun Greeting() {
Column {
val toggleState = remember {
mutableStateOf(false)
}
AnimatedVisibility(visible = toggleState.value) {
Text(text = "Edit", fontSize = 64.sp)
}
ToggleButton(toggleState = toggleState) {}
}
}
#Composable
fun ToggleButton(modifier: Modifier = Modifier,
toggleState: MutableState<Boolean>,
onToggle: (Boolean) -> Unit) {
TextButton(
modifier = modifier,
onClick = {
toggleState.value = !toggleState.value
onToggle(toggleState.value)
})
{ Text(text = if (toggleState.value) "Stop" else "Start") }
}
One thing I didn't like the code is val toggleState = remember { ... }.
I prefer val toggleState by remember {...}
However, if I do that, as shown below, I cannot pass the toggleState over to ToggleButton, as ToggleButton wanted mutableState<Boolean> and not Boolean. Hence it will error out.
#Composable
fun Greeting() {
Column {
val toggleState by remember {
mutableStateOf(false)
}
AnimatedVisibility(visible = toggleState) {
Text(text = "Edit", fontSize = 64.sp)
}
ToggleButton(toggleState = toggleState) {} // Here will have error
}
}
#Composable
fun ToggleButton(modifier: Modifier = Modifier,
toggleState: MutableState<Boolean>,
onToggle: (Boolean) -> Unit) {
TextButton(
modifier = modifier,
onClick = {
toggleState.value = !toggleState.value
onToggle(toggleState.value)
})
{ Text(text = if (toggleState.value) "Stop" else "Start") }
}
How can I fix the above error while still using val toggleState by remember {...}?
State hoisting in Compose is a pattern of moving state to a composable's caller to make a composable stateless. The general pattern for state hoisting in Jetpack Compose is to replace the state variable with two parameters:
value: T: the current value to display
onValueChange: (T) -> Unit: an event that requests the value to change, where T is the proposed new value
You can do something like
// stateless composable is responsible
#Composable
fun ToggleButton(modifier: Modifier = Modifier,
toggle: Boolean,
onToggleChange: () -> Unit) {
TextButton(
onClick = onToggleChange,
modifier = modifier
)
{ Text(text = if (toggle) "Stop" else "Start") }
}
and
#Composable
fun Greeting() {
var toggleState by remember { mutableStateOf(false) }
AnimatedVisibility(visible = toggleState) {
Text(text = "Edit", fontSize = 64.sp)
}
ToggleButton(toggle = toggleState,
onToggleChange = { toggleState = !toggleState }
)
}
You can also add the same stateful composable which is only responsible for holding internal state:
#Composable
fun ToggleButton(modifier: Modifier = Modifier) {
var toggleState by remember { mutableStateOf(false) }
ToggleButton(modifier,
toggleState,
onToggleChange = {
toggleState = !toggleState
},
)
}