Jetpack Compose - layouting reusable components - kotlin

for practicing with reusable components in Jetpack Compose, I started a little exercise.
See picture below.
As I imagine the green row, the input row, and the rows between have the same construction.
The first element got the available space, the second takes 50.dp, and the last one got 70.dp.
I tried to seperate the width into variables an pass this vars as modifiers to the single elements in the row. I thought if I need additionally fields, the I can extend it whitout any problem.
CODE DOESN'T WORK!
#Composable
fun groundComponent(
modifier: Modifier = Modifier,
spaceBetween: Dp = 0.dp,
color: Color,
content: #Composable () -> Unit
) {
Surface(
color = color
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(spaceBetween)
) {
content()
}
}
}
#Composable
fun inputSection() {
val firstRowWidth = 1F
val secondRowWidth = 70.dp
val thirdRowWidth = 50.dp
Text("Add Ingredient")
groundComponent(color = Color.Green){
Text( text="Ingredient", modifier = Modifier.weight(firstRowWidth ))
Text( text="Amount", modifier = Modifier.widthIn(secondRowWidth ))
Text( text="Unit", modifier = Modifier.widthIn(thirdRowWidth ))
}
groundComponent{
Text( text="Sugar", modifier = Modifier.weight(firstRowWidth ))
Text( text="500", modifier = Modifier.widthIn(secondRowWidth ))
Text( text="gr", modifier = Modifier.widthIn(thirdRowWidth ))
}
groundComponent{
Text( text="Carrot", modifier = Modifier.weight(firstRowWidth ))
Text( text="1.5", modifier = Modifier.widthIn(secondRowWidth ))
Text( text="kg", modifier = Modifier.widthIn(thirdRowWidth ))
}
groundComponent{
TextField(
value = "newIngredient",
onValueChange = {},
modifier = Modifier.weight(firstRowWidth ))
TextField(
value = "newAmount",
onValueChange = {},
modifier = Modifier.widthIn(secondRowWidth )
)
TextField(
value = "newUnit",
onValueChange = {},
modifier = Modifier.widthIn(thirdRowWidth )
)
}
Button(onClick={}){Text("add")}
}
I got several errors with the .weight modifier.
So how is the right aproach to solve such a situation.
Thanks!

Modifier.weight is a Modifier that defined in specific scopes such as RowScope and ColumnScope. To be able to use modifiers that are defined in specific scopes you need to add Receiver to your content. BoxScope as Modifier.align() that is defined for instance, you can define your scopes either.
#Composable
fun GroundComponent(
modifier: Modifier = Modifier,
spaceBetween: Dp = 0.dp,
color: Color=Color.Unspecified,
content: #Composable RowScope.() -> Unit
) {
Surface(
color = color
) {
// Can't call content here because it has RowScope as receiver
// content()
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(spaceBetween)
) {
content()
}
}
}
Also in InputSection you define weight fractions as
val firstRowWidth = 1F
val secondRowWidth = 70.dp
val thirdRowWidth = 50.dp
these values should be proportionate to each other
if you set 1/5/6 for instance. or between 0f-1f
And by convention you can name Composable with capital initial letter since they are considered as widgets.

Thanks for your reply and your pretty good explanation!
With your help I solved my problem this way.
#Composable
fun InputRowGroundComponent(
modifier: Modifier = Modifier,
spaceBetweenElements: Dp = 0.dp,
color: Color,
content: #Composable RowScope.() -> Unit
) {
Surface(
color = color
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(spaceBetweenElements),
verticalAlignment = Alignment.CenterVertically
) {
content()
}
}
}
#Composable
fun OverviewHeader(
modifier: Modifier = Modifier,
text: String
) {
Text(
modifier = modifier,
text = text,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center
)
}
#Composable
fun OverviewContent(
modifier: Modifier = Modifier,
text: String
) {
Text(
modifier = modifier,
text = text,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
}
#Preview(showBackground = true, widthDp = 460)
#Composable
fun testPrev() {
val rowWeights = listOf(6F,3F,2F)
val rowSpacing = 8.dp
val indentation = 10.dp
Column(
modifier = Modifier.padding(8.dp),
verticalArrangement = Arrangement.spacedBy(rowSpacing)
) {
InputRowGroundComponent(
modifier = Modifier.heightIn(45.dp),
spaceBetweenElements = rowSpacing,
color = Color.Green
) {
OverviewHeader(text = "Ingredient", modifier = Modifier.weight(rowWeights[0]))
OverviewHeader(text = "Amount", modifier = Modifier.weight(rowWeights[1]))
OverviewHeader(text = "Unit", modifier = Modifier.weight(rowWeights[2]))
}
InputRowGroundComponent(
modifier = Modifier.heightIn(30.dp),
spaceBetweenElements = rowSpacing,
color = Color.Unspecified
) {
OverviewContent(text = "Sugar", modifier = Modifier.weight(rowWeights[0]).padding(start=indentation))
OverviewContent(text = "500", modifier = Modifier.weight(rowWeights[1]).padding(start=indentation))
OverviewContent(text = "gr", modifier = Modifier.weight(rowWeights[2]).padding(start=indentation))
}
InputRowGroundComponent(
modifier = Modifier.heightIn(30.dp),
spaceBetweenElements = rowSpacing,
color = Color.Unspecified
) {
OverviewContent(text = "Carrot", modifier = Modifier.weight(rowWeights[0]).padding(start=indentation))
OverviewContent(text = "1.5", modifier = Modifier.weight(rowWeights[1]).padding(start=indentation))
OverviewContent(text = "kg", modifier = Modifier.weight(rowWeights[2]).padding(start=indentation))
}
InputRowGroundComponent(
spaceBetweenElements = rowSpacing,
color = Color.Unspecified
) {
TextField(value = "", onValueChange = {}, modifier = Modifier.weight(rowWeights[0]))
TextField(value = "", onValueChange = {}, modifier = Modifier.weight(rowWeights[1]))
TextField(value = "", onValueChange = {}, modifier = Modifier.weight(rowWeights[2]))
}
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { /*Todo*/ },
content = {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = "Add Ingredient"
)
Text(
text = "Add"
)
}
}
)
}
}
Is this approach now right?

Related

add scroll - jetpack compose

I need to add a scroll to a page.
but i get this error: Vertically scrollable component was measured with an infinity maximum height constraints, which is disallowed.
I do have Lazy Column inside a big column.. and I think that is the problem.
but i need the whole page to scroll.
here is my code:
#Composable
fun ContactDetails(contactViewModel: ContactViewModel, contactId: String?) {
val contact = contactViewModel.getContactById(contactId)
val optionsScrollState = rememberScrollState()
Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(state = optionsScrollState),
horizontalAlignment = Alignment.Start
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(350.dp)
) {
Card(modifier = Modifier.fillMaxWidth()) {
if (contact[0].image != null) {
Image(
painter = rememberImagePainter(data = contact[0].image),
contentDescription = "Image",
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.Crop
)
} else {
Box(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.background(color = Color.Magenta),
contentAlignment = Alignment.Center,
) {
Text(
text = contact[0].first_name[0].toString(),
fontSize = 26.sp,
fontWeight = FontWeight.Bold
)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(),
contentAlignment = Alignment.BottomStart
) {
Text(
text = "${contact.first().first_name} ${contact.first().last_name}",
modifier = Modifier,
color = Color.White,
style = MaterialTheme.typography.h4,
fontWeight = FontWeight.Bold
)
}
}
}
Divider(
color = Color.LightGray,
thickness = 2.dp,
)
phoneCard(contact = contact.first(), contactViewModel)
emailCard(contact = contact.first(), contactViewModel)
}
}
#Composable
fun phoneCard(contact: Contact, contactViewModel: ContactViewModel) {
val phoneList = contactViewModel.getPhoneListByContactId(contact.id)
Row(modifier = Modifier.padding(10.dp)) {
Icon(imageVector = Icons.Default.Phone, contentDescription = "Phone Icon")
Text(
text = "Phone:",
modifier = Modifier.padding(start = 10.dp),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.h6,
fontWeight = FontWeight.Bold
)
}
LazyColumn {
items(items = phoneList) {
Row(modifier = Modifier.padding(10.dp)) {
Text(text = it.type + ": ", fontWeight = FontWeight.Bold)
Text(text = it.number)
}
}
}
}
#Composable
fun emailCard(contact: Contact, contactViewModel: ContactViewModel) {
val emailList = contactViewModel.getEmailListByContactId(contact.id)
Row(modifier = Modifier.padding(10.dp)) {
Icon(imageVector = Icons.Default.Email, contentDescription = "Email Icon")
Text(
text = "Email:",
modifier = Modifier.padding(start = 10.dp),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.h6,
fontWeight = FontWeight.Bold
)
}
LazyColumn {
items(items = emailList) {
Row(modifier = Modifier.padding(10.dp)) {
Text(text = it.type + ": ", fontWeight = FontWeight.Bold)
Text(text = it.address)
}
}
}
}
here is my emulator:
my description page
Specify a height in your LazyColumns for example:
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
) {

How stop BottomSheet from Overlaying BottomNavBar (with Jetpack Compose)

This is my first time using the BottomSheet.
I follow a tutorial and implemented the bottomsheet in a code with bottomnavbar. This was the result. {The bottomsheet overlaps the bottomnavbar, when it is collapsed}
As you can see the bottomsheet overlays the bottomnavbar.
Here's the code of the bottomSheet
#SuppressLint("UnusedMaterialScaffoldPaddingParameter")
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun BottomSheetLayout() {
val bottomSheetItems = listOf(
BottomSheetItem(title = "Notification", icon = Icons.Default.Notifications),
...
)
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed)
)
val coroutineScope = rememberCoroutineScope()
BottomSheetScaffold(
scaffoldState = bottomSheetScaffoldState,
sheetShape = RoundedCornerShape(topEnd = 30.dp),
sheetContent = {
Column(
content = {
Spacer(modifier = Modifier.padding(16.dp))
Text(
text = "Create New",
modifier = Modifier
.fillMaxWidth(),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
fontSize = 21.sp,
color = Color.White
)
LazyVerticalGrid(
//cells = GridCells.Fixed(3),
columns = GridCells.Fixed(3)
) {
items(bottomSheetItems.size, itemContent = {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.padding(top = 24.dp)
.clickable {
},
) {
Spacer(modifier = Modifier.padding(8.dp))
Icon(
bottomSheetItems[it].icon,
bottomSheetItems[it].title,
tint = Color.White
)
Spacer(modifier = Modifier.padding(8.dp))
Text(text = bottomSheetItems[it].title, color = Color.White)
}
})
}
}, modifier = Modifier
.fillMaxWidth()
.height(350.dp)
//.background(MaterialTheme.colors.primary)
.background(
brush = Brush.linearGradient(colors = listOf(MaterialTheme.colors.primary, Color.White)
)
)
.padding(16.dp)
)
},
) {
Column(modifier = Modifier.fillMaxSize()) {
Button(
modifier = Modifier
.padding(20.dp),
onClick = {
coroutineScope.launch {
if (bottomSheetScaffoldState.bottomSheetState.isCollapsed) {
bottomSheetScaffoldState.bottomSheetState.expand()
} else {
bottomSheetScaffoldState.bottomSheetState.collapse()
}
}
}
) {
Text(
text = "Click to show Bottom Sheet"
)
}
}
}
}
Thanks for your help in advance.
I'd also appreciate any tip and advice about the bottomSheet, thanks again.

how to make Parralel scroll in jetpack compose?

I am makeing a profile screen in jetpack compose
I want to make the Blue background behind the image scroll up when the image is scrolled up because it doesn't look good :)
This is what I need help with, see the gif to understand better.
how can I achieve what I want, that the blue background will scroll up also?
I tried moving the background box around the code,
out of the column at the head
out of the column at the TopBar
out of the column at the ProfileSection
but it didn't work because the column rearrange the objects so that they are ontop of each other and I just want it from behind
source code
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.politi_cal.R
#Composable
fun CelebProfileScreen() {
BlackBackgroundSquare()
Column(modifier = Modifier.fillMaxSize()) {
LazyColumn(content = {
item {
TopBar()
Spacer(modifier = Modifier.height(60.dp))
ProfileSection(
name = "Amit Segal",
company = "N12 news channel",
)
// voting bar
VotingBar(
leftyPercent = 10, rightyPercent = 90
)
// lazy column for more info
MoreInfo("Amit Segal is a journalist and a news anchor. He is the host of the N12 news channel. He is a very popular journalist. Amit Yitzchak Segal[1] (born Biz in Nisan 5, 1982, April 10, 1982) is an Israeli journalist, radio and television personality. Serves as the political commentator of the news company and a political columnist in the \"Yediot Aharonot\" newspaper. One of the most influential journalists in Israel[2]. Presents Meet the Press on Channel 12 together with Ben Caspit.")
}
})
}
}
#Composable
fun MoreInfo(information_param: String, modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(start = 26.dp, end = 26.dp)) {
Text(
text = "More information",
color = Color.Black,
fontSize = 36.sp,
fontWeight = FontWeight.Bold
)
Text(
text = information_param,
color = Color.Black,
fontSize = 24.sp,
fontWeight = FontWeight.Normal,
maxLines = 10,
overflow = TextOverflow.Ellipsis
)
}
}
#Composable
fun VotingBar(
modifier: Modifier = Modifier, leftyPercent: Int, rightyPercent: Int
) {
var leftyPercentWeight: Float = (leftyPercent / 10).toFloat()
var rightyPercentWeight: Float = (rightyPercent / 10).toFloat()
val shape = RoundedCornerShape(32.dp)
Column(
Modifier.padding(start = 16.dp, end = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
modifier = modifier
.fillMaxWidth()
.height(32.dp)
.background(Color.White)
.clip(shape)
.border(1.dp, Color.Black, shape)
) {
Column(
// add rounded corners to the left side
modifier = Modifier
.background(Color(0xFFA60321))
.weight(rightyPercentWeight)
.clip(CircleShape)
.fillMaxHeight(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
}
Column(
modifier = Modifier
.background(Color(0xFF03588C))
.fillMaxHeight(leftyPercentWeight)
.weight(1f)
.clip(CircleShape),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
// add rounded corners to the right side
) {
}
}
// second row
// stack over flow https://stackoverflow.com/questions/74619069/what-is-the-attribute-of-the-moddifier-that-i-need-to-change-to-make-the-corners?noredirect=1#comment131712293_74619069
Column(
Modifier.padding(start = 46.dp, end = 46.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(Color.White),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Row {
Box(
modifier = Modifier
.size(30.dp)
.clip(CircleShape)
.background(Color(0xFFA60321))
)
Spacer(modifier = Modifier.width(10.dp))
Text(
text = "Right $rightyPercent%",
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
}
Row() {
Box(
modifier = Modifier
.size(30.dp)
.clip(CircleShape)
.background(Color(0xFF03588C))
)
Spacer(modifier = Modifier.width(10.dp))
Text(
text = "Left $leftyPercent%", fontSize = 20.sp, fontWeight = FontWeight.Bold
)
}
}
}
}
}
#Composable
fun BlackBackgroundSquare() {
Box(
// modifier fill only half the screen
modifier = Modifier
.fillMaxWidth()
.height(300.dp)
// insert background color as hex
.background(Color(0xFF2C3E50))
)
}
#Composable
fun TopBar(
modifier: Modifier = Modifier
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = modifier.fillMaxWidth()
) {
Text(
text = "Profile",
overflow = TextOverflow.Ellipsis,
fontWeight = FontWeight.Bold,
fontSize = 40.sp,
color = Color.White
)
}
}
#Composable
fun ProfileSection(
name: String, company: String, modifier: Modifier = Modifier
) {
Column(
modifier = modifier.fillMaxWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
RoundImage(
image = painterResource(id = R.drawable.profile_pic), modifier = Modifier.size(250.dp)
)
Text(
text = name,
overflow = TextOverflow.Ellipsis,
fontWeight = FontWeight.Bold,
fontSize = 40.sp,
color = Color.Black
)
Text(
text = company,
overflow = TextOverflow.Ellipsis,
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
color = Color.Black
)
}
}
#Composable
fun RoundImage(
image: Painter, modifier: Modifier = Modifier
) {
Image(
painter = image,
contentDescription = "Profile image",
modifier = modifier
.aspectRatio(1f, matchHeightConstraintsFirst = true)
.border(
width = 6.dp, color = Color.White, shape = CircleShape
)
.padding(3.dp)
.clip(CircleShape)
)
}
You do not need to use LazyColumn, if it contains only a single item, but Box is a simple component for placing components on top of each other, which could be used here for the item like this:
item {
Box {
BlackBackgroundSquare()
Column {
TopBar()
Spacer(modifier = Modifier.height(60.dp))
ProfileSection(
name = "Amit Segal",
company = "N12 news channel",
)
}
}
}
Add a state to LazyColumn, then use state.firstVisibleItemIndex to detect when first item is not visible. Use a DisposableEffect to detect index by index to prevent lag. When first idx is consumed, hide your upper bar. Could use a viewmodel to save first index. Then show action bar if first item is visible again.
DisposableEffect(key1 = listState.firstVisibleItemIndex) {
onDispose {
viewModel
.setFirstVisibleItemIdx(listState.firstVisibleItemIndex)
}
}
On enter get the index from viewmodel and scroll. If no value, just show as normal.
LaunchedEffect(viewModel.firstVisibleItemIdx) {
listState.scrollToItem(viewModel.firstVisibleItemIdx.value ?: 0)
}

How to make BottomNavigationItem fill space available?

I want to make BottomNavigation with text appearing from right side of selected item. How can I make BottomNavigationItem fill available space or move other items, to prevent text from wrapping?
here's image
Tried this, but didn't work:
#Composable
fun BottomNavigationBar(
items: List<BottomNavItem>,
navController: NavController,
onItemClick: (BottomNavItem) -> Unit
) {
val backStackEntry = navController.currentBackStackEntryAsState()
BottomNavigation(
modifier = Modifier,
elevation = 0.dp,
backgroundColor = light
) {
items.forEach{
val selected = it.screen_route == backStackEntry.value?.destination?.route
BottomNavigationItem(
selected = selected,
selectedContentColor = primary_color,
unselectedContentColor = shaded,
onClick = { onItemClick(it) },
icon = {
Row(
modifier = if (selected) Modifier
.fillMaxWidth()
.padding(horizontal = 15.dp)
else Modifier
.padding(horizontal = 15.dp)
) {
Icon(
imageVector = it.icon,
contentDescription = it.title,
tint = if (selected) primary_color else shaded,
)
if (selected){
Text(
text = it.title,
color = primary_color,
textAlign = TextAlign.Center,
fontSize = 20.sp,
modifier = Modifier.padding(start = 2.dp).align(Alignment.CenterVertically),
overflow = TextOverflow.Visible
)
}
}
}
)
}
}
}
You can check solution from Jetsnack sample app. I think this is the same behavior you want to achieve.

How send the value to child compose which is viewmodel value?

Here is my code.
DrawerCompose
#Composable
fun DrawerCompose(
modifier: Modifier = Modifier,
onDestinationClicked: (route: String) -> Unit
) {
val versionName = BuildConfig.VERSION_NAME
val empno by remember {
mutableStateOf("")
}
val password by remember {
mutableStateOf("")
}
Column(
modifier = Modifier
.background(MaterialTheme.colors.primarySurface)
.fillMaxSize()
) {
Box(
modifier = Modifier
.fillMaxWidth()
) {
Column(
modifier = Modifier
.padding(20.dp)
) {
Text(
text = "Title",
fontSize = 25.sp,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
color = Color.White
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "ID",
fontSize = 25.sp,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
color = Color.White
)
OutlinedTextField(
value = empno,
onValueChange = {
},
modifier = Modifier
.fillMaxWidth(),
label = { Text(text = "ID") },
singleLine = true,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
),
keyboardActions = KeyboardActions(
onNext = {
}
)
)
Text(
text = "Password",
fontSize = 25.sp,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
color = Color.White
)
OutlinedTextField(
value = password,
onValueChange = {
},
modifier = Modifier
.fillMaxWidth(),
label = { Text(text = "Password") },
singleLine = true,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = {
}
)
)
Text(
text = "v $versionName",
fontSize = 25.sp,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
color = Color.White
)
}
}
}
}
MenuListScreen
#OptIn(ExperimentalFoundationApi::class)
#Composable
fun HomeScreen(
navController: NavController,
onNavigateToMenuDetailScreen: (String) -> Unit,
viewModel: MenuListViewModel,
) {
val context = LocalContext.current
val menuList = viewModel.menuList.value
val scope = rememberCoroutineScope()
val scaffoldState = rememberScaffoldState(
rememberDrawerState(initialValue = DrawerValue.Closed)
)
val loading = viewModel.loading.value
val dialogQueue = viewModel.dialogQueue
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBarCompose(
title = "Title",
navigationIcon = {
IconButton(onClick = {
scope.launch {
scaffoldState.drawerState.open()
}
}) {
Icon(imageVector = Icons.Filled.Menu, contentDescription = "")
}
}
)
},
scaffoldState = scaffoldState,
drawerContent = {
DrawerCompose(
onDestinationClicked = { route ->
scope.launch {
scaffoldState.drawerState.close()
}
}
)
},
drawerGesturesEnabled = true
) {
MenuList(
loading = loading,
menus = menuList,
onNavigateToSubmenuScreen = onNavigateToMenuDetailScreen
)
}
}
MenuListViewModel
#HiltViewModel
class MenuListViewModel #Inject constructor(
private val restoreMenus: RestoreMenus,
private val assetsManager: AssetsManager,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val id: MutableState<String> = mutableStateOf("")
val password: MutableState<String> = mutableStateOf("")
fun onChangeEmpNo(id: String) {
this.id.value = id
}
fun onChangePassword(password: String) {
this.password.value = password
}
}
If the value in the ID TextField in the drawer changes, the empno value in the viewModel changes, and if the value in the ID in the viewModel changes, the value in the ID TextField in the drawer changes.
I'd like to know how to communicate with Viewmodel and Child Compose.
Edit
I solved that pass the viewmodel variables to drawer, and viewmodel function
You have to pass callback function
#Composable
fun DrawerCompose(
modifier: Modifier = Modifier,
onDestinationClicked: (route: String) -> Unit,
onIdChange: (value: String) -> Unit,
onPasswordChange: (value: String) -> Unit,
) {
val versionName = BuildConfig.VERSION_NAME
val empno by remember {
mutableStateOf("")
}
val password by remember {
mutableStateOf("")
}
Column(
modifier = Modifier
.background(MaterialTheme.colors.primarySurface)
.fillMaxSize()
) {
Box(
modifier = Modifier
.fillMaxWidth()
) {
Column(
modifier = Modifier
.padding(20.dp)
) {
Text(
text = "Title",
fontSize = 25.sp,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
color = Color.White
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "ID",
fontSize = 25.sp,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
color = Color.White
)
OutlinedTextField(
value = empno,
onValueChange = onIdChange,
modifier = Modifier
.fillMaxWidth(),
label = { Text(text = "ID") },
singleLine = true,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
),
keyboardActions = KeyboardActions(
onNext = {
}
)
)
Text(
text = "Password",
fontSize = 25.sp,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
color = Color.White
)
OutlinedTextField(
value = password,
onValueChange = onPasswordChange,
modifier = Modifier
.fillMaxWidth(),
label = { Text(text = "Password") },
singleLine = true,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = {
}
)
)
Text(
text = "v $versionName",
fontSize = 25.sp,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
color = Color.White
)
}
}
}
}
After that pass those function from main screen
#OptIn(ExperimentalFoundationApi::class)
#Composable
fun HomeScreen(
navController: NavController,
onNavigateToMenuDetailScreen: (String) -> Unit,
viewModel: MenuListViewModel,
) {
val context = LocalContext.current
val menuList = viewModel.menuList.value
val scope = rememberCoroutineScope()
val scaffoldState = rememberScaffoldState(
rememberDrawerState(initialValue = DrawerValue.Closed)
)
val loading = viewModel.loading.value
val dialogQueue = viewModel.dialogQueue
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBarCompose(
title = "Title",
navigationIcon = {
IconButton(onClick = {
scope.launch {
scaffoldState.drawerState.open()
}
}) {
Icon(imageVector = Icons.Filled.Menu, contentDescription = "")
}
}
)
},
scaffoldState = scaffoldState,
drawerContent = {
DrawerCompose(
onDestinationClicked = { route ->
scope.launch {
scaffoldState.drawerState.close()
}
},
viewModel.onChangeEmpNo,
viewModel.onChangePassword
)
},
drawerGesturesEnabled = true
) {
MenuList(
loading = loading,
menus = menuList,
onNavigateToSubmenuScreen = onNavigateToMenuDetailScreen
)
}
}