I am trying to set up navigation for the drawer icons/textfield in jetpack compose but not exactly sure how to do it properly. How can I set the navigation so that whenever I click in one of the Icons I get navigated to that composable screen? This is currently my MainDrawer layout:
#Composable
fun MainDrawer() {
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
topBar = {
AppBar(
onNavigationIconClick = {
scope.launch {
scaffoldState.drawerState.isOpen
}
}
)
},
drawerContent = {
DrawerHeader()
DrawerBody(
items = listOf(
MenuItem(
id = "item1",
title = "item1",
contentDescription = "Go to item1 screen",
icon = Icons.Default.Home
),
MenuItem(
id = "item2",
title = "item2",
contentDescription = "Go to item2 screen",
icon = Icons.Default.Settings
),
MenuItem(
id = "item3",
title = "item3",
contentDescription = "Ge to item3",
icon = Icons.Default.Info
),
MenuItem(
id = "item4",
title = "item4",
contentDescription = "Go to Your item4",
icon = Icons.Default.Info
),
MenuItem(
id = "item5",
title = "item5",
contentDescription = "Your item5",
icon = Icons.Default.Info
),
MenuItem(
id = "item6",
title = "item6",
contentDescription = "Your item6",
icon = Icons.Default.Info
),
MenuItem(
id = "item7",
title = "item7",
contentDescription = "item7",
icon = Icons.Default.Info
),
MenuItem(
id = "item8",
title = "item8",
contentDescription = "item8",
icon = Icons.Default.Info
),
)
) {
println("Clicked on ${it.title}")
}
}
) {
}
}
Drawer Body:
This contains the element of the body
#Composable
fun DrawerBody(
items: List<MenuItem>,
modifier: Modifier = Modifier,
itemTextStyle: TextStyle = TextStyle(fontSize = 18.sp),
onItemClick: (MenuItem) -> Unit
) {
LazyColumn(modifier) {
items(items) { item ->
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
onItemClick(item)
}
.padding(16.dp)
) {
Icon(
imageVector = item.icon,
contentDescription = item.contentDescription
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = item.title,
style = itemTextStyle,
modifier = Modifier.weight(1f)
)
}
}
}
}
I managed to solve this issue. This is how it will work:
https://gyazo.com/4c32e855becff72f8979650545ad7f39
This is how I did it:
Begin by adding the dependencies to your project:
// Navigation with Compose
implementation "androidx.navigation:navigation-compose:2.5.0"
// Modal Drawer Layout
implementation "androidx.drawerlayout:drawerlayout:1.1.1"
// Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
Start by creating the TopAppBar for the app. This is the following code:
#Composable
fun TopBar(scope: CoroutineScope, scaffoldState: ScaffoldState) {
TopAppBar(
title = { Text(text = stringResource(R.string.app_name), fontSize = 18.sp) },
navigationIcon = {
IconButton(onClick = {
scope.launch {
scaffoldState.drawerState.open()
}
}) {
Icon(Icons.Filled.Menu, "")
}
},
backgroundColor = colorResource(id = R.color.purple_200),
contentColor = Color.White
)
}
Next, create a sealed class. This class will represent the items you want inside your drawer. I put the following as an example:
sealed class NavDrawerItem(var route: String, var icon: Int, var title: String) {
object Add : NavDrawerItem("add", android.R.drawable.ic_menu_add, "Add")
object Edit : NavDrawerItem("edit", android.R.drawable.ic_menu_edit, "Edit")
object Search : NavDrawerItem("search", android.R.drawable.ic_menu_search, "Search")
object Location : NavDrawerItem("location", android.R.drawable.ic_menu_mylocation, "Location")
object Preferences : NavDrawerItem("preferences", android.R.drawable.ic_menu_preferences, "Preferences")
}
Now, create the highlighter. When an item in the drawer has been pressed, this will highlight what item has been pressed.
#Composable
fun DrawerItem(item: NavDrawerItem, selected: Boolean, onItemClick: (NavDrawerItem) -> Unit) {
val background = if (selected) R.color.purple_200 else android.R.color.transparent
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = { onItemClick(item) })
.height(45.dp)
.background(colorResource(id = background))
.padding(start = 10.dp)
) {
Image(
painter = painterResource(id = item.icon),
contentDescription = item.title,
colorFilter = ColorFilter.tint(Color.White),
contentScale = ContentScale.Fit,
modifier = Modifier
.height(35.dp)
.width(35.dp)
)
Spacer(modifier = Modifier.width(7.dp))
Text(
text = item.title,
fontSize = 18.sp,
color = Color.White
)
}
}
Here we create each screen for the respective items. 1 composable screen for each item. I created an easy and quick screen but you can do your own screens here.
#Composable
fun AddScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.background(colorResource(id = R.color.myColor))
.wrapContentSize(Alignment.Center)
) {
Text(
text = "Add Screen",
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.align(Alignment.CenterHorizontally),
textAlign = TextAlign.Center,
fontSize = 25.sp
)
}
}
#Composable
fun EditScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.background(colorResource(id = R.color.myOrange))
.wrapContentSize(Alignment.Center)
) {
Text(
text = "Edit Screen",
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.align(Alignment.CenterHorizontally),
textAlign = TextAlign.Center,
fontSize = 25.sp
)
}
}
#Composable
fun SearchScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.background(colorResource(id = R.color.purple_500))
.wrapContentSize(Alignment.Center)
) {
Text(
text = "Search Screen",
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.align(Alignment.CenterHorizontally),
textAlign = TextAlign.Center,
fontSize = 25.sp
)
}
}
#Composable
fun LocationScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.background(colorResource(id = R.color.myGreen))
.wrapContentSize(Alignment.Center)
) {
Text(
text = "Location Screen",
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.align(Alignment.CenterHorizontally),
textAlign = TextAlign.Center,
fontSize = 25.sp
)
}
}
#Composable
fun PreferencesScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.background(colorResource(id = R.color.myGreen))
.wrapContentSize(Alignment.Center)
) {
Text(
text = "Preference Screen",
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.align(Alignment.CenterHorizontally),
textAlign = TextAlign.Center,
fontSize = 25.sp
)
}
}
Now we need to create the actual navigation using a navcontroller and navHostController. We do this in the following way:
#Composable
fun ComposeNavigation(navController: NavHostController) {
NavHost(navController, startDestination = NavDrawerItem.Add.route) {
composable(NavDrawerItem.Add.route) {
AddScreen()
}
composable(NavDrawerItem.Edit.route) {
EditScreen()
}
composable(NavDrawerItem.Search.route) {
SearchScreen()
}
composable(NavDrawerItem.Location.route) {
LocationScreen()
}
composable(NavDrawerItem.Preferences.route) {
PreferencesScreen()
}
}
}
After, create DrawerLayout with all the items, coroutines and navbackstack.
#Composable
fun DrawerLayout(scope: CoroutineScope, scaffoldState: ScaffoldState, navController: NavController) {
val items = listOf(
NavDrawerItem.Add,
NavDrawerItem.Edit,
NavDrawerItem.Search,
NavDrawerItem.Location,
NavDrawerItem.Preferences
)
Column {
// Header
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = R.drawable.ic_launcher_foreground.toString(),
modifier = Modifier
.height(100.dp)
.fillMaxWidth()
.padding(10.dp)
)
Spacer(
modifier = Modifier
.fillMaxWidth()
.height(5.dp)
)
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
items.forEach { item ->
DrawerItem(item = item, selected = currentRoute == item.route, onItemClick = {
navController.navigate(item.route) {
navController.graph.startDestinationRoute?.let { route ->
popUpTo(route) {
saveState = true
}
}
launchSingleTop = true
restoreState = true
}
scope.launch {
scaffoldState.drawerState.close()
}
})
}
}
}
Finally we create the mainLayout that will work as the main frame for our app. Here will use a scaffold for the topbar and drawercontent:
#SuppressLint("UnusedMaterialScaffoldPaddingParameter")
#Composable
fun MainLayout() {
val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed))
val scope = rememberCoroutineScope()
val navController = rememberNavController()
Scaffold(
scaffoldState = scaffoldState,
topBar = { TopBar(scope = scope, scaffoldState = scaffoldState) },
drawerBackgroundColor = colorResource(id = R.color.myColor),
drawerContent = {
DrawerLayout(scope = scope, scaffoldState = scaffoldState, navController = navController)
},
) {
ComposeNavigation(navController = navController)
}
}
Hope this helped anyone looking for a functional navigation drawer! Feel free to give it a arrowUp if this works for you. Happy Coding!
Related
ISSUE
I am trying to align the text Enable Notifications and the switch on the same line. If I were to use XML layouts, then I would have gone with constraint layout, but I am trying to figure out how to do this with Compose.
SET UP
#Composable
fun Settings() {
val viewModel: SettingsViewModel = viewModel()
val uiState: SettingsState = viewModel.uiState.collectAsState().value
SettingsList(uiState = uiState, viewModel)
}
#Composable
fun SettingsList(uiState: SettingsState, viewModel: SettingsViewModel, modifier: Modifier = Modifier) {
val scaffoldState = rememberScaffoldState()
Scaffold(
modifier = Modifier, scaffoldState = scaffoldState,
backgroundColor = MaterialTheme.colors.background,
topBar = {
TopAppBar(
modifier = Modifier.semantics {
heading()
},
backgroundColor = MaterialTheme.colors.surface,
contentPadding = PaddingValues(start = 12.dp)
) {
Icon(
tint = MaterialTheme.colors.onSurface,
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(id = R.string.header_settings_back)
)
Spacer(modifier = modifier.width(16.dp))
Text(
text = stringResource(id = R.string.header_settings),
fontSize = 18.sp,
color = MaterialTheme.colors.onSurface
)
}
}
) {
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.verticalScroll(scrollState).padding(paddingValues = it)
) {
NotificationSettingsComposable(uiState.notificationsEnabled, {
viewModel.toggleNotificationSettings()
}, Modifier.fillMaxWidth())
}
}
}
#Composable
fun NotificationSettingsComposable(checked: Boolean, onCheckedChanged: (Boolean) -> Unit, modifier: Modifier = Modifier) {
Surface(modifier = modifier.padding(12.dp)) {
Row {
Text(
text = stringResource(id = R.string.body_enable_notifications), fontSize = 16.sp,
color = MaterialTheme.colors.onSurface,
)
Switch(checked = checked, onCheckedChange = onCheckedChanged, modifier = Modifier.align(Alignment.Top))
}
}
}
However the text and the switch are not being aligned on the same line.
DESIRED OUTCOME
The text and the switch should be aligned on the same line as shown in this picture.
This is what I am getting
What I want to achieve
Add verticalAlignment = Alignment.CenterVertically in the Row:
Something like:
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Enable Notification", fontSize = 16.sp,
)
Switch(
checked = checkedState.value,
onCheckedChange = { checkedState.value = it }
)
}
I was deveoping an Kotlin Multiplatform app, where I develop the UI using Jetpack Compose framework, where I recover some data from network using graphql, to keep it into a local database build with SQLDelight.
My poblem comes when I try to implement a remove operation into my localDatabase, due to the LazyColumn component doesn't update after remove some item.
I saw there are some solution using SnapshotStateList, but I don't see how to implemented, and I try to do it using a mutableState of a boolean variable, which tells me if there some change which need to be update, but the Composable doesn't recompose neither.
this is my main component where I added the list:
#Composable
fun MyCharactersView(viewModel: MainViewModel, bottomBar: #Composable () -> Unit, popBack: () -> Unit, navController: NavController, characterSelect: (character: CharacterModel) -> Unit) {
val isLoading = viewModel.isLoading.collectAsState()
val loadError = viewModel.loadError.collectAsState()
val charactersBought = viewModel.characterBought.collectAsState()
val charactersBoughtSnapShot : SnapshotStateList<List<CharacterModel>> = SnapshotStateList(charactersBought.value)
LaunchedEffect(key1 = Unit){
viewModel.getAllBoughtCharacters().also {
Log.d("info", "Lanza el getAllBoughtCharacters:count: ${charactersBought.value.size}")
}
}
when {
isLoading.value -> {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
CircularProgressIndicator(
color = MaterialTheme.colors.primary,
modifier = Modifier
.fillMaxWidth(0.4f)
.fillMaxHeight(0.4f)
.align(Alignment.Center)
)
}
}
loadError.value != "" -> {
Text(
text = "Se ha producido un error",
color = Color.Red,
textAlign = TextAlign.Center
)
}
else -> {
if(viewModel.updateList.value){
viewModel.getAllBoughtCharacters().also {
Log.d("info", "Llama al viewModel.updateList.value: ${charactersBought.value}")
}
}
Scaffold(
topBar = {
TopAppBar(
title = { Text(
text = "MyCharacters",
style = TextStyle(
color = Color.White,
fontStyle = FontStyle.Italic,
textAlign = TextAlign.Center
)
)},
navigationIcon = {
IconButton(onClick = { popBack() }) {
Icon(Icons.Filled.ArrowBack, contentDescription = "Back")
}
}
)
},
bottomBar = bottomBar)
{ paddingValues ->
Surface(
modifier = Modifier
.background(
Color.Transparent
)
) {
if(charactersBought.value.isEmpty()){
Text(
text = "You don't have any character jet.\nTry to buy some one",
fontStyle = FontStyle.Italic,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
modifier = Modifier.padding(start = (LocalConfiguration.current.screenWidthDp/4).dp, end = 0.dp, top = (LocalConfiguration.current.screenHeightDp/4).dp)
)
}else{
LazyColumn(
contentPadding = paddingValues,
modifier = Modifier
.background(Color.Transparent)
) {
Log.d("info", "CharacterResponse in items function: ${charactersBought.value}")
items(charactersBought.value) { character ->
MyCharactersListRowView(
navController = navController,
viewModel = viewModel,
characterModel = character!!,
characterSelected = characterSelect
)
}
}
}
}
}
}
}
}
And this is my ListRow component:
#OptIn(ExperimentalFoundationApi::class)
#Composable
fun MyCharactersListRowView(
navController: NavController,
viewModel: MainViewModel,
characterModel: CharacterModel,
characterSelected: (character : CharacterModel) -> Unit
) {
val openDialog = remember { mutableStateOf(false) }
if(openDialog.value) {
SimpleAlertDialog(
show = true,
onConfirm = { viewModel.deleteCharacter(characterModel.id.toLong().also {
if(viewModel.rowAffected.value.toInt() != 0) {
Toast.makeText(appContext, "${characterModel.name} remove correctly !", Toast.LENGTH_LONG).show().also {
openDialog.value = false
viewModel.updateList.value = true
}
}
}) },
onDismiss = { openDialog.value = false },
textDescription = "Would you like to remove ${characterModel.name} from your characters?",
textTittle = "Remove"
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = { characterSelected(characterModel) })
.padding(vertical = 8.dp, horizontal = 16.dp)
.combinedClickable(
onLongClick = {
openDialog.value = true
},
onClick = {
navController.navigate(Screens.CharacterDetailsScreen.route + "/${characterModel.id}")
}
),
verticalAlignment = Alignment.CenterVertically
){
AsyncImage(
model = characterModel.image,
contentDescription = characterModel.name,
contentScale = ContentScale.Fit,
modifier = Modifier
.size(60.dp)
.clip(CircleShape)
)
Column(modifier = Modifier.weight(1F)) {
Text(
text = characterModel.name,
style = MaterialTheme.typography.h5,
fontWeight = FontWeight.Bold
)
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) {
Text(
text = "Seen in ${characterModel.numberEpisodes} episodes, most frequently in ${characterModel.location}",
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Thin
)
}
}
}
Divider()
}
So if you knows some way to force to recompose a composable, (which I think is not possible), or a working solution using the SnapShotStateList, and can share, take thanks in advance !
I have a compile time error as shown in the title
(#Composable invocations can only happen from the context of a #Composable function),
I have one card in this code so I want to show An AlertDialog after clicking the card clickable,
I know that this problem is exist in the platform,
So how can i solve this problem?
#Composable
fun Screen_A_with_WithTopBar(navController: NavController) {
Scaffold(
topBar = {
TopAppBar(
title = {
Text(text = "Top App Bar")
},
navigationIcon = {
IconButton(onClick = {
navController.navigate(Screen.Home.route)
}) {
Icon(Icons.Filled.Menu, "backIcon")
}
},
backgroundColor = MaterialTheme.colors.primary,
contentColor = Color.White,
elevation = 10.dp
)
}, content = {
Screen_A(navController)
}
)
}
#Composable
fun Screen_A(navController: NavController) {
val context = LocalContext.current
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
//.background(ScreenColor)
) {
Text(
text = "Dooro",
fontSize = 42.sp
)
Spacer(modifier = Modifier.height(33.dp))
Row(
verticalAlignment = Alignment.Top,
horizontalArrangement = Arrangement.SpaceAround,
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
) {
Card(
modifier = Modifier
.width(150.dp)
.height(80.dp)
.clickable {
Alert()
},
RoundedCornerShape(7.dp),
elevation = 7.dp
//.padding(40.dp)
) {
Text(
text = "None",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center
)
}
} // END Row
}
}
#Composable
fun Alert() {
val openDialog = remember { mutableStateOf(true) }
if (openDialog.value) {
AlertDialog(
onDismissRequest = {
openDialog.value = false
},
title = {
Text(text = "Title")
},
text = {
Text(
"This area typically contains the supportive text " +
"which presents the details regarding the Dialog's purpose."
)
},
buttons = {
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { openDialog.value = false }
) {
Text("Dismiss")
}
}
}
)
}
}
Here you can use mutableStateOf to be able to show your Alert.
on Screen_A, you should add the following
var showAlert by mutableStateOf(false)
#Composable
private fun ShowAlert() {
if (showAlert)
Alert()
}
Finally in your Card, you will change the value of showAlert field to true.
Card(
modifier = Modifier
.width(150.dp)
.height(80.dp)
.clickable {
showAlert = true
},
RoundedCornerShape(7.dp),
elevation = 7.dp
//.padding(40.dp)
)
I am creating a profile page for the app and need to perform different actions based upon items clicked inside LazyColumn.
What I want is when I click on Rate us, play store to open, click on feedback browser should open, clicking on settings user should navigate to the settings screen. How to perform such different actions in LazyColumn click listener.
Here is code,
ProfileListModel.kt
data class ProfileListModel(
val imageVector: ImageVector,
#StringRes val contentDescription: Int,
#StringRes val text: Int
)
ProfileListRepository.kt
class ProfileListRepository {
fun getAllData(): List<ProfileListModel> {
return listOf(
ProfileListModel(
imageVector = Icons.Default.InvertColors,
contentDescription = R.string.app_theme,
text = R.string.app_theme
),
ProfileListModel(
imageVector = Icons.Default.NoAccounts,
contentDescription = R.string.turn_on_incognito,
text = R.string.turn_on_incognito
),
ProfileListModel(
imageVector = Icons.Default.Language,
contentDescription = R.string.app_language,
text = R.string.app_language
),
ProfileListModel(
imageVector = Icons.Default.Settings,
contentDescription = R.string.settings,
text = R.string.settings
),
ProfileListModel(
imageVector = Icons.Default.ModeOfTravel,
contentDescription = R.string.drive_mode,
text = R.string.drive_mode
),
ProfileListModel(
imageVector = Icons.Default.ChildCare,
contentDescription = R.string.child_mode,
text = R.string.child_mode
),
ProfileListModel(
imageVector = Icons.Default.BarChart,
contentDescription = R.string.time_watched,
text = R.string.time_watched
),
ProfileListModel(
imageVector = Icons.Default.Storage,
contentDescription = R.string.account_data,
text = R.string.account_data
),
ProfileListModel(
imageVector = Icons.Default.StarRate,
contentDescription = R.string.rate_us_on_play_store,
text = R.string.rate_us_on_play_store
),
ProfileListModel(
imageVector = Icons.Default.Feedback,
contentDescription = R.string.feedback,
text = R.string.feedback
),
ProfileListModel(
imageVector = Icons.Default.Forum,
contentDescription = R.string.forum,
text = R.string.forum
),
ProfileListModel(
imageVector = Icons.Default.HelpCenter,
contentDescription = R.string.help_center,
text = R.string.help_center
),
ProfileListModel(
imageVector = Icons.Default.PrivacyTip,
contentDescription = R.string.privacy_policy,
text = R.string.privacy_policy
),
ProfileListModel(
imageVector = Icons.Default.ControlPointDuplicate,
contentDescription = R.string.terms_of_service,
text = R.string.terms_of_service
)
)
}
}
ProfileListItem.kt
#Composable
fun ProfileListItem(profileListModel: ProfileListModel) {
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.clickable(
onClick = {
/*todo("perform different actions based upon item clicked")*/
}
)
/*.clickable { selectedItem(profileListModel) }*/
.padding(all = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = profileListModel.imageVector,
contentDescription = stringResource(id = profileListModel.contentDescription),
tint = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.padding(end = 16.dp)
)
Text(
modifier = Modifier.wrapContentSize(),
text = stringResource(id = profileListModel.text)
)
}
}
ProfileScreen.kt
#ExperimentalMaterial3Api
#Composable
fun ProfileScreen() {
Box(modifier = Modifier.fillMaxSize()) {
Surface(color = MaterialTheme.colorScheme.background) {
ProfileList()
}
}
}
#ExperimentalMaterial3Api
#Composable
fun ProfileList() {
val profileListRepository = ProfileListRepository()
val getAllData = profileListRepository.getAllData()
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
) {
item {
ProfileCard()
}
item {
NightLight()
}
itemsIndexed(items = getAllData) { index, profileListModel ->
ProfileListItem(profileListModel = profileListModel)
}
}
}
#Composable
fun NightLight() {
val checkedState = remember { mutableStateOf(false) }
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.clickable(onClick = { checkedState.value = !checkedState.value })
.padding(horizontal = 16.dp, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(modifier = Modifier.wrapContentSize(), verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = Icons.Default.ModeNight,
contentDescription = stringResource(id = R.string.night_light),
tint = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.padding(end = 16.dp)
)
Text(
modifier = Modifier.wrapContentSize(),
text = stringResource(id = R.string.night_light)
)
}
Switch(checked = checkedState.value, onCheckedChange = { checkedState.value = it })
}
}
#ExperimentalMaterial3Api
#Composable
fun ProfileCard(
modifier: Modifier = Modifier
) {
Card(
modifier = modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(all = 16.dp),
shape = RoundedCornerShape(size = 16.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)
) {
Column(
modifier = Modifier.padding(all = 16.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(text = "Click to login")
Image(
painter = painterResource(id = R.drawable.avatar),
contentDescription = "avatar",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(size = 72.dp)
.clip(shape = CircleShape)
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(text = "Level 0")
Card(
modifier = Modifier.wrapContentSize(),
shape = RoundedCornerShape(size = 16.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 3.dp)
) {
Text(
text = "Check in",
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
)
}
}
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(top = 16.dp)
) {
Column(
modifier = Modifier
.wrapContentHeight()
.weight(1f)
) {
Text(text = "0")
Text(text = "Social reputation")
}
Column(
modifier = Modifier
.wrapContentHeight()
.weight(1f)
) {
Text(text = "0", modifier = Modifier.fillMaxWidth())
Text(text = "Credit score", modifier = Modifier.fillMaxWidth())
}
}
}
}
}
#ExperimentalMaterial3Api
#Preview(
showBackground = true, name = "Light mode",
uiMode = Configuration.UI_MODE_NIGHT_NO or Configuration.UI_MODE_TYPE_NORMAL
)
/*#Preview(
showBackground = true, name = "Night mode",
uiMode = Configuration.UI_MODE_NIGHT_YES or Configuration.UI_MODE_TYPE_NORMAL
)*/
#Composable
fun ProfileScreenPreview() {
ProfileScreen()
}
You can pass in an id to identify the item being clicked and then when the item is clicked, pass that id back to your viewmodel. This is what uni-directional flow is about in Compose. Example:
#ExperimentalMaterialApi
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivity(intent)
setContent {
ProfileList(
onItemClick = {itemId ->
viewmodel.handleItem(itemId)
}
)
}
}
}
#Composable
fun ProfileList(
onItemClick: (itemId: MenuItemIDs) -> Unit
) {
val profileListRepository = ProfileListRepository()
val getAllData = profileListRepository.getAllData()
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
) {
item {
ProfileCard(
itemId = MenuItemIDs.ProfilCard,
onItemClick = onItemClick
)
}
item {
NightLight()
}
itemsIndexed(items = getAllData) { index, profileListModel ->
ProfileListItem(profileListModel = profileListModel)
}
}
}
#Composable
fun ProfileCard(
modifier: Modifier = Modifier,
itemId: MenuItemIDs,
onItemClick: (itemId: MenuItemIDs) -> Unit
) {
Card(
modifier = modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(all = 16.dp)
.clickable {
onItemClick(itemId)
}
) {
}
}
enum class MenuItemIDs {
ProfilCard,
NightLight,
AppTheme,
blah, blah, blah...
}
I have the following composable function to build a Chip:
#Composable
fun CategoryChip(
category: String,
isSelected: Boolean = false,
onSelectedCategoryChanged: (String) -> Unit,
onExecuteSearch: () -> Unit
) {
Surface(
modifier = Modifier.padding(end = 8.dp, bottom = 8.dp),
elevation = 8.dp,
shape = RoundedCornerShape(16.dp),
color = when {
isSelected -> colorResource(R.color.teal_200)
else -> colorResource(R.color.purple_500)
}
) {
Row(modifier = Modifier
.toggleable(
value = isSelected,
onValueChange = {
onSelectedCategoryChanged(category)
onExecuteSearch()
}
)) {
Text(
text = category,
style = MaterialTheme.typography.body2,
color = Color.White,
modifier = Modifier.padding(8.dp)
)
}
}
}
This creates the following chip:
But what I am trying to achieve is the following:
Is it possible to create a shape like that with Jetpack Compose?
Starting with M2 1.2.0-alpha02 you can use the Chip or FilterChip composable:
Chip(
onClick = { /* Do something! */ },
border = BorderStroke(
ChipDefaults.OutlinedBorderSize,
Color.Red
),
colors = ChipDefaults.chipColors(
backgroundColor = Color.White,
contentColor = Color.Red
),
leadingIcon = {
Icon(
Icons.Filled.Settings,
contentDescription = "Localized description"
)
}
) {
Text("Change settings")
}
With M3 (androidx.compose.material3) you can use one of these options:
AssistChip
FilterChip
InputChip
SuggestionChip
Something like:
AssistChip(
onClick = { /* Do something! */ },
label = { Text("Assist Chip") },
leadingIcon = {
Icon(
Icons.Filled.Settings,
contentDescription = "Localized description",
Modifier.size(AssistChipDefaults.IconSize)
)
}
)
Yes, all you have to do is add a border to your surface.
Surface(
modifier = Modifier.padding(end = 8.dp, bottom = 8.dp),
elevation = 8.dp,
shape = RoundedCornerShape(16.dp),
border = BorderStroke(
width = 1.dp,
color = when {
isSelected -> colorResource(R.color.teal_200)
else -> colorResource(R.color.purple_500)
}
)
)
Building on Code Poet's answer i wanted to show how to do a Material Chip with background color:
#Composable
fun buildChip(label: String, icon: ImageVector? = null) {
Box(modifier = Modifier.padding(8.dp)) {
Surface(
elevation = 1.dp,
shape = MaterialTheme.shapes.small,
color = Color.LightGray
) {
Row(verticalAlignment = Alignment.CenterVertically) {
if (icon != null) Icon(
icon,
modifier = Modifier
.fillMaxHeight()
.padding(horizontal = 4.dp)
)
Text(
label,
modifier = Modifier.padding(8.dp),
style = MaterialTheme.typography.button.copy(color = Color.DarkGray)
)
}
}
}
}
Simply use ChipDefaults.outlinedBorder and Defaults.outlinedChipColors():
Chip(
onClick = {},
border = ChipDefaults.outlinedBorder,
colors = ChipDefaults.outlinedChipColors(),
) {
Text(
text = "Trends",
)
}
compose version 1.2.1 , kotlinCompilerExtensionVersion 1.3.1
Also add accompanist Flow library
implementation
"com.google.accompanist:accompanist-flowlayout:0.26.4-beta"
We will use SuggestionChip composable function to create chips.
#Composable
fun SuggestionChipLayout() {
val chips by remember { mutableStateOf(listOf("India", "France", "Spain","Netherland","Austarlia","Nepal")) }
var chipState by remember { mutableStateOf("") }
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Suggestion Chip Example")
Spacer(modifier = Modifier.height(20.dp))
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp),
mainAxisSpacing = 16.dp,
crossAxisSpacing = 16.dp,
) {
chips.forEach {
SuggestionChipEachRow(chip = it, it == chipState) { chip ->
chipState = chip
}
}
}
}
}
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun SuggestionChipEachRow(
chip: String,
selected: Boolean,
onChipState: (String) -> Unit
) {
SuggestionChip(onClick = {
if (!selected)
onChipState(chip)
else
onChipState("")
}, label = {
Text(text = chip)
},
border = SuggestionChipDefaults.suggestionChipBorder(
borderWidth = 1.dp,
borderColor = if (selected) Color.Transparent else PurpleGrey40
),
modifier = Modifier.padding(horizontal = 16.dp),
colors = SuggestionChipDefaults.suggestionChipColors(
containerColor = if (selected) Purple80 else Color.Transparent
),
shape = RoundedCornerShape(16.dp)
)
}
Filter Chips: In filter chip we can select multiple items at a time
compose version 1.2.1 , kotlinCompilerExtensionVersion 1.3.1 Also add accompanist Flow library
implementation
"com.google.accompanist:accompanist-flowlayout:0.26.4-beta"
we will use FilterChip composable function to create Filter Chip
#SuppressLint("MutableCollectionMutableState")
#Composable
fun FilterChipLayout() {
val originalChips by remember {
mutableStateOf(
listOf(
"chip1",
"chip2",
"chip3",
"chip4",
"chip5"
)
)
}
val temp: Set<Int> = emptySet()
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Filter Chip Example")
Spacer(modifier = Modifier.height(20.dp))
FilterChipEachRow(chipList = originalChips, tempList = temp)
}
}
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun FilterChipEachRow(
chipList: List<String>,
tempList: Set<Int>
) {
var multipleChecked by rememberSaveable { mutableStateOf(tempList) }
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp),
mainAxisSpacing = 16.dp,
crossAxisSpacing = 16.dp,
) {
chipList.forEachIndexed { index, s ->
FilterChip(selected = multipleChecked.contains(index), onClick = {
multipleChecked = if (multipleChecked.contains(index))
multipleChecked.minus(index)
else
multipleChecked.plus(index)
}, label = {
Text(text = s)
},
border = FilterChipDefaults.filterChipBorder(
borderColor = if (!multipleChecked.contains(index)) PurpleGrey40 else Color.Transparent,
borderWidth = if (multipleChecked.contains(index)) 0.dp else 2.dp
),
shape = RoundedCornerShape(8.dp),
leadingIcon = {
(if (multipleChecked.contains(index)) Icons.Default.Check else null)?.let {
Icon(
it,
contentDescription = ""
)
}
}
)
}
}
}