How to implement search in Jetpack Compose - Android - android

var noteListState by remember { mutableStateOf(listOf("Drink water", "Walk")) }
This is my list of items. Any leads would be appreciated

To Get a Main UI with list and searchview
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainScreen()
}
}
For Main()
#Composable
fun MainScreen() {
val textState = remember { mutableStateOf(TextFieldValue("")) }
Column {
SearchView(textState)
ItemList(state = textState)
}
}
For Serchview and list
#Composable
fun SearchView(state: MutableState<TextFieldValue>) {
TextField(
value = state.value,
onValueChange = { value ->
state.value = value
},
modifier = Modifier.fillMaxWidth(),
textStyle = TextStyle(color = Color.White, fontSize = 18.sp),
leadingIcon = {
Icon(
Icons.Default.Search,
contentDescription = "",
modifier = Modifier
.padding(15.dp)
.size(24.dp)
)
},
trailingIcon = {
if (state.value != TextFieldValue("")) {
IconButton(
onClick = {
state.value =
TextFieldValue("") // Remove text from TextField when you press the 'X' icon
}
) {
Icon(
Icons.Default.Close,
contentDescription = "",
modifier = Modifier
.padding(15.dp)
.size(24.dp)
)
}
}
},
singleLine = true,
shape = RectangleShape, // The TextFiled has rounded corners top left and right by default
colors = TextFieldDefaults.textFieldColors(
textColor = Color.White,
cursorColor = Color.White,
leadingIconColor = Color.White,
trailingIconColor = Color.White,
backgroundColor = MaterialTheme.colors.primary,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent
)
)
}
#Composable
fun ItemList(state: MutableState<TextFieldValue>) {
val items by remember { mutableStateOf(listOf("Drink water", "Walk")) }
var filteredItems: List<String>
LazyColumn(modifier = Modifier.fillMaxWidth()) {
val searchedText = state.value.text
filteredItems = if (searchedText.isEmpty()) {
items
} else {
val resultList = ArrayList<String>()
for (item in items) {
if (item.lowercase(Locale.getDefault())
.contains(searchedText.lowercase(Locale.getDefault()))
) {
resultList.add(item)
}
}
resultList
}
items(filteredItems) { filteredItem ->
ItemListItem(
ItemText = filteredItem,
onItemClick = { /*Click event code needs to be implement*/
}
)
}
}
}
#Composable
fun ItemListItem(ItemText: String, onItemClick: (String) -> Unit) {
Row(
modifier = Modifier
.clickable(onClick = { onItemClick(ItemText) })
.background(colorResource(id = R.color.purple_700))
.height(57.dp)
.fillMaxWidth()
.padding(PaddingValues(8.dp, 16.dp))
) {
Text(text = ItemText, fontSize = 18.sp, color = Color.White)
}
}
For More Detailed answer you can refer this link

Please could you explain more, this question is a bit short and unclear.
One way (i think you could do) is just to loop through the list and check if a element (of your list) contains that text.
val filterPattern = text.toString().lowercase(Locale.getDefault()) // text you are looking for
for (item in copyList) { // first make a copy of the list
if (item.name.lowercase(Locale.getDefault()).contains(filterPattern)) {
filteredList.add(item) // if the item contains that text add it to the list
}
}
... // here you then override noteListState
After you have made the filteredList you can just override the noteListState. Make sure to make a copy of that list beforehand and set it back when you don't want to show the filtered results.

Related

My lazycolumn items and my textfield glide on the screen when the keyboard is opened in jetpack compose

I'm just at the beginning of a chat bot application and I'm showing dialogs with lazycolumn. I also have a textfield, the bot responds to the texts entered in this textfield and cards appear on the screen, but when the keyboard is opened, my lazycolumn items slide upwards and the textfield does not appear properly. To solve this, I used something called bringIntoViewRequester and
I added the following to
my
manifest android:windowSoftInputMode="adjustResize"
MainActivity window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
I'll share my codes below.
How can I solve this problem? also this method as far as I know is experimental, in my opinion if there is another non experimental method you know it would be better
my first screen
var index = 1
#Composable
fun FirstScreen() {
val list = remember { mutableStateListOf<Message>() }
val botList = listOf("Peter", "Francesca", "Luigi", "Igor")
val random = (0..3).random()
val botName: String = botList[random]
val hashMap: HashMap<String, String> = HashMap<String, String>()
customBotMessage(message = Message1, list)
LazyColumn {
items(list.size) { i ->
if (list[i].id == RECEIVE_ID)
Item(message = list[i], botName)
else
Item(message = list[i], "User")
}
}
SimpleOutlinedTextFieldSample(list, hashMap)
}
#OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
#Composable
fun SimpleOutlinedTextFieldSample(
list: SnapshotStateList<Message>,
hashMap: HashMap<String, String>
) {
val coroutineScope = rememberCoroutineScope()
val keyboardController = LocalSoftwareKeyboardController.current
val bringIntoViewRequester = remember { BringIntoViewRequester() }
var text by remember { mutableStateOf("") }
Column(
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.bringIntoViewRequester(bringIntoViewRequester)
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.Center
) {
OutlinedTextField(
modifier = Modifier
.padding(8.dp)
.onFocusEvent { focusState ->
if (focusState.isFocused) {
coroutineScope.launch {
bringIntoViewRequester.bringIntoView()
}
}
},
keyboardOptions = KeyboardOptions(imeAction = androidx.compose.ui.text.input.ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }),
value = text,
onValueChange = { text = it },
label = { Text("send message") })
IconButton(onClick = {
if (text.isNotEmpty()) {
list.add(
Message(
text,
SEND_ID,
Timestamp(System.currentTimeMillis()).toString()
)
)
hashMap.put(listOfMessageKeys[index - 1], text)
customBotMessage(listOfMessages[index], list)
index += 1
}
text = ""
}) {
Icon(
modifier = Modifier.padding(8.dp),
painter = painterResource(id = R.drawable.ic_baseline_send_24),
contentDescription = "send message img"
)
}
}
}
}
private fun customBotMessage(message: String, list: SnapshotStateList<Message>) {
GlobalScope.launch {
delay(1000)
withContext(Dispatchers.Main) {
list.add(Message(message, RECEIVE_ID, Timestamp(System.currentTimeMillis()).toString()))
}
}
}
LazyColumn - Item
#OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
#Composable
fun Item(
message : Message,
person : String,
){
val coroutineScope = rememberCoroutineScope()
val keyboardController = LocalSoftwareKeyboardController.current
val bringIntoViewRequester = remember { BringIntoViewRequester() }
Card(
modifier = Modifier
.padding(10.dp)
.bringIntoViewRequester(bringIntoViewRequester),
elevation = 10.dp
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
modifier = Modifier
.padding(10.dp)
.height(IntrinsicSize.Min)){
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(4.dp)
){
Text(
buildAnnotatedString {
withStyle(style = SpanStyle(fontWeight = FontWeight.Light)) {
append("$person: ")
}
},
modifier = Modifier
.padding(4.dp)
.onFocusEvent { focusState ->
if (focusState.isFocused) {
coroutineScope.launch {
bringIntoViewRequester.bringIntoView()
}
}
}
)
Text(
buildAnnotatedString {
withStyle(
style = SpanStyle(
fontWeight = FontWeight.W900,
color = Color(0xFF4552B8)
))
{
append(message.message)
}
},
modifier = Modifier.onFocusEvent { focusState ->
if (focusState.isFocused) {
coroutineScope.launch {
bringIntoViewRequester.bringIntoView()
}
}
}
)
}
}
}
}

How to select multiple items in LazyColumn in JetpackCompose

How to select multiple items in LazyColumn and finally add the selected items in a seperate list.
GettingTags(tagsContent ={ productTags ->
val flattenList = productTags.flatMap {
it.tags_list
}
Log.i(TAG,"Getting the flattenList $flattenList")
LazyColumn{
items(flattenList){
ListItem(text = {Text(it) })
if(selectedTagItem) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = "Selected",
tint = Color.Green,
modifier = Modifier.size(20.dp)
)
}
}
}
})
Mutable variable state
var selectedTagItem by remember{
mutableStateOf(false)
}
First create a class with selected variable to toggle
#Immutable
data class MyItem(val text: String, val isSelected: Boolean = false)
Then create a SnapshotStateList via mutableStateListOf that contains all of the items, and can trigger recomposition when we update any item with new instance, add or remove items also. I used a ViewModel but it's not mandatory. We can toggle items using index or get selected items by filtering isSelected flag
class MyViewModel : ViewModel() {
val myItems = mutableStateListOf<MyItem>()
.apply {
repeat(15) {
add(MyItem(text = "Item$it"))
}
}
fun getSelectedItems() = myItems.filter { it.isSelected }
fun toggleSelection(index: Int) {
val item = myItems[index]
val isSelected = item.isSelected
if (isSelected) {
myItems[index] = item.copy(isSelected = false)
} else {
myItems[index] = item.copy(isSelected = true)
}
}
}
Create LazyColumn with key, key makes sure that only updated items are recomposed, as can be seen in performance document
#Composable
private fun SelectableLazyListSample(myViewModel: MyViewModel) {
val selectedItems = myViewModel.getSelectedItems().map { it.text }
Text(text = "Selected items: $selectedItems")
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(8.dp)
) {
itemsIndexed(
myViewModel.myItems,
key = { _, item: MyItem ->
item.hashCode()
}
) { index, item ->
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color.Red, RoundedCornerShape(8.dp))
.clickable {
myViewModel.toggleSelection(index)
}
.padding(8.dp)
) {
Text("Item $index", color = Color.White, fontSize = 20.sp)
if (item.isSelected) {
Icon(
modifier = Modifier
.align(Alignment.CenterEnd)
.background(Color.White, CircleShape),
imageVector = Icons.Default.Check,
contentDescription = "Selected",
tint = Color.Green,
)
}
}
}
}
}
Result

Sorting List Items in LazyColumn - Android Jetpack Compose

Trying to implement a feature where the user is able to select a way to sort the list of Breweries when a certain item in a dropdown menu is selected (Name, City, Address). I am having trouble figuring out what I am doing wrong. Whenever I select example "Name" in the dropdown menu, the list of breweries does not sort. I am new at this so any advice will really help. Thank you!
Here is the full code for the MainScreen -
#SuppressLint("UnusedMaterialScaffoldPaddingParameter")
#RequiresApi(Build.VERSION_CODES.O)
#Composable
fun MainScreen(
navController: NavController,
mainViewModel: MainViewModel,
search: String?
) {
val apiData = brewData(mainViewModel = mainViewModel, search = search)
Scaffold(
content = {
if (apiData.loading == true){
Column(
modifier = Modifier
.fillMaxSize()
.background(colorResource(id = R.color.grey_blue)),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator()
}
} else if (apiData.data != null){
MainContent(bData = apiData.data!!, viewModel = mainViewModel)
}
},
topBar = { BrewTopBar(navController, search) }
)
}
#Composable
fun BrewTopBar(navController: NavController, search: String?) {
TopAppBar(
modifier = Modifier
.height(55.dp)
.fillMaxWidth(),
title = {
Text(
stringResource(id = R.string.main_title),
style = MaterialTheme.typography.h5,
maxLines = 1
)
},
actions = {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(text = "$search")
IconButton(onClick = { navController.navigate(Screens.SearchScreen.name) }) {
Icon(
modifier = Modifier.padding(10.dp),
imageVector = Icons.Filled.Search,
contentDescription = stringResource(id = R.string.search)
)
}
}
},
backgroundColor = colorResource(id = R.color.light_purple)
)
}
#RequiresApi(Build.VERSION_CODES.O)
#Composable
fun MainContent(bData: List<BrewData>, viewModel: MainViewModel){
val allBreweries = bData.size
var sortByName by remember { mutableStateOf(false) }
var sortByCity by remember { mutableStateOf(false) }
var sortByAddress by remember { mutableStateOf(false) }
var dataSorted1 = remember { mutableStateOf(bData.sortedBy { it.name }) }
var dataSorted: MutableState<List<BrewData>>
Column(
modifier = Modifier
.fillMaxSize()
.background(colorResource(id = R.color.grey_blue))
){
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically
){
// Amount text label
Text(
text = "Result(s): ${bData.size}",
modifier = Modifier.padding(top = 15.dp, start = 15.dp, bottom = 5.dp)
)
SortingMenu(sortByName, sortByCity, sortByAddress) // needs mutable booleans for sorting
}
// List of Brewery cards
LazyColumn(
Modifier
.fillMaxSize()
.padding(5.dp)
){
items(allBreweries){ index ->
Breweries(
bData = when {
sortByName == true -> remember { mutableStateOf(bData.sortedBy { it.name }) }
sortByCity == true -> remember { mutableStateOf(bData.sortedBy { it.city }) }
sortByAddress == true -> remember { mutableStateOf(bData.sortedBy { it.address_2 }) }
else -> remember { mutableStateOf(bData) }
} as MutableState<List<BrewData>>, // Todo: create a way to select different sorting conditions
position = index,
viewModel = viewModel
)
}
}
}
}
#RequiresApi(Build.VERSION_CODES.O)
#Composable
fun Breweries(
bData: MutableState<List<BrewData>>,
position: Int,
viewModel: MainViewModel
){
val cardNumber = position+1
val cityApiData = bData.value[position].city
val phoneNumberApiData = bData.value[position].phone
val countryApiData = bData.value[position].country
val breweryTypeApiData = bData.value[position].brewery_type
val countyApiData = bData.value[position].county_province
val postalCodeApiData = bData.value[position].postal_code
val stateApiData = bData.value[position].state
val streetApiData = bData.value[position].street
val apiLastUpdated = bData.value[position].updated_at
val context = LocalContext.current
val lastUpdated = apiLastUpdated?.let { viewModel.dateTextConverter(it) }
val websiteUrlApiData = bData.value[position].website_url
var expanded by remember { mutableStateOf(false) }
val clickableWebsiteText = buildAnnotatedString {
if (websiteUrlApiData != null) {
append(websiteUrlApiData)
}
}
val clickablePhoneNumberText = buildAnnotatedString {
if (phoneNumberApiData != null){
append(phoneNumberApiData)
}
}
Column(
Modifier.padding(10.dp)
) {
//Brewery Card
Card(
modifier = Modifier
.padding(start = 15.dp, end = 15.dp)
// .fillMaxSize()
.clickable(
enabled = true,
onClickLabel = "Expand to view details",
onClick = { expanded = !expanded }
)
.semantics { contentDescription = "Brewery Card" },
backgroundColor = colorResource(id = R.color.light_blue),
contentColor = Color.Black,
border = BorderStroke(0.5.dp, colorResource(id = R.color.pink)),
elevation = 15.dp
) {
Column(verticalArrangement = Arrangement.Center) {
//Number text for position of card
Text(
text = cardNumber.toString(),
modifier = Modifier.padding(15.dp),
fontSize = 10.sp,
)
// Second Row
BreweryTitle(bData = bData, position = position)
// Third Row
// Brewery Details
CardDetails(
cityApiData = cityApiData,
stateApiData = stateApiData,
streetApiData = streetApiData,
countryApiData = countryApiData,
countyApiData = countyApiData,
postalCodeApiData = postalCodeApiData,
breweryTypeApiData = breweryTypeApiData,
lastUpdated = lastUpdated,
expanded = expanded
)
//Fourth Row
Row(horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
){
Column(
modifier = Modifier.padding(
start = 10.dp, end = 10.dp,
top = 15.dp, bottom = 15.dp
),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
//Phone Number Link
LinkBuilder(
clickablePhoneNumberText,
phoneNumberApiData,
modifier = Modifier.padding(bottom = 10.dp)
) {
if (phoneNumberApiData != null) {
viewModel.callNumber(phoneNumberApiData, context)
}
}
//Website Link
LinkBuilder(
clickableWebsiteText,
websiteUrlApiData,
modifier = Modifier.padding(bottom = 15.dp),
intentCall = {
if (websiteUrlApiData != null) {
viewModel.openWebsite(websiteUrlApiData, context)
}
}
)
}
}
}
}
}
}
#Composable
fun CardDetails(
cityApiData: String?,
stateApiData: String?,
streetApiData: String?,
countryApiData: String?,
countyApiData: String?,
postalCodeApiData: String?,
breweryTypeApiData: String?,
lastUpdated: String?,
expanded: Boolean
){
// Third Row
//Brewery Details
Column(
modifier = Modifier.padding(
start = 30.dp, end = 10.dp, top = 25.dp, bottom = 15.dp
),
verticalArrangement = Arrangement.Center
) {
if (expanded) {
Text(text = "City: $cityApiData")
Text(text = "State: $stateApiData")
Text(text = "Street: $streetApiData")
Text(text = "Country: $countryApiData")
Text(text = "County: $countyApiData")
Text(text = "Postal Code: $postalCodeApiData")
Text(text = "Type: $breweryTypeApiData")
Text(text = "Last updated: $lastUpdated")
}
}
}
#Composable
fun BreweryTitle(bData: MutableState<List<BrewData>>, position: Int){
// Second Row
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
// Name of Brewery
Text(
text = bData.value[position].name!!,
modifier = Modifier.padding(start = 15.dp, end = 15.dp),
fontWeight = FontWeight.Bold,
maxLines = 3,
textAlign = TextAlign.Center,
softWrap = true,
style = TextStyle(
color = colorResource(id = R.color.purple_500),
fontStyle = FontStyle.Normal,
fontSize = 17.sp,
fontFamily = FontFamily.SansSerif,
letterSpacing = 2.sp,
)
)
}
}
}
#Composable
fun LinkBuilder(
clickableText: AnnotatedString,
dataText: String?,
modifier: Modifier,
intentCall: (String?) -> Unit
){
if (dataText != null){
ClickableText(
text = clickableText,
modifier = modifier,
style = TextStyle(
textDecoration = TextDecoration.Underline,
letterSpacing = 2.sp
),
onClick = {
intentCall(dataText)
}
)
}
else {
Text(
text = "Sorry, Not Available",
color = Color.Gray,
fontSize = 10.sp
)
}
}
//Gets data from view model
#Composable
fun brewData(
mainViewModel: MainViewModel, search: String?
): DataOrException<List<BrewData>, Boolean, Exception> {
return produceState<DataOrException<List<BrewData>, Boolean, Exception>>(
initialValue = DataOrException(loading = true)
) {
value = mainViewModel.getData(search)
}.value
}
#Composable
fun SortingMenu(sortByName: Boolean, sortByCity: Boolean, sortByAddress: Boolean,) {
var expanded by remember { mutableStateOf(false) }
val items = listOf("Name", "City", "Address")
val disabledValue = "B"
var selectedIndex by remember { mutableStateOf(0) }
Box(
modifier = Modifier
.wrapContentSize(Alignment.TopStart)
) {
Text(
text = "Sort by: ${items[selectedIndex]}",
modifier = Modifier
.clickable(onClick = { expanded = true })
.width(120.dp)
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false // todo: sort list data when clicked
when(selectedIndex){
0 -> sortByName == true
1 -> sortByCity == true
2 -> sortByAddress == true
} }
) {
items.forEachIndexed { index, text ->
DropdownMenuItem(onClick = {
selectedIndex = index
expanded = false
}) {
val disabledText = if (text == disabledValue) {
" (Disabled)"
} else {
""
}
Text(text = text + disabledText)
}
}
}
}
}
//not used
fun sorting(menuList: List<String>, dataList: List<BrewData>, index: Int){
when{
menuList[index] == "Name" -> dataList.sortedBy { it.name }
menuList[index] == "City" -> dataList.sortedBy { it.city }
menuList[index] == "Address" -> dataList.sortedBy { it.address_2 }
}
}
It's really difficult to track this much code especially on my 13 inch screen.
You should move your sort logic to ViewModel or useCase and create a sort function you can call on user interaction such as viewmodel.sort(sortType) and update value of one MutableState with new or use mutableStateListOf and update with sorted list.
Sorting is business logic and i would do it in a class that doesn't contain any Android related dependencies, i use MVI or MVVM so my preference is a UseCase class which i can unit test sorting with any type and a sample list and desired outcomes.
You can do it with Comparatorand Modifier.animateItemPlacement() so it would look even nicer!
I will post an example from Google so you can understand the logic:
#Preview
#OptIn(ExperimentalFoundationApi::class)
#Composable
fun PopularBooksDemo() {
MaterialTheme {
var comparator by remember { mutableStateOf(TitleComparator) }
Column {
Row(
modifier = Modifier.height(IntrinsicSize.Max),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
"Title",
Modifier.clickable { comparator = TitleComparator }
.weight(5f)
.fillMaxHeight()
.padding(4.dp)
.wrapContentHeight(Alignment.CenterVertically),
textAlign = TextAlign.Center
)
Text(
"Author",
Modifier.clickable { comparator = AuthorComparator }
.weight(2f)
.fillMaxHeight()
.padding(4.dp)
.wrapContentHeight(Alignment.CenterVertically),
textAlign = TextAlign.Center
)
Text(
"Year",
Modifier.clickable { comparator = YearComparator }
.width(50.dp)
.fillMaxHeight()
.padding(4.dp)
.wrapContentHeight(Alignment.CenterVertically),
textAlign = TextAlign.Center
)
Text(
"Sales (M)",
Modifier.clickable { comparator = SalesComparator }
.width(65.dp)
.fillMaxHeight()
.padding(4.dp)
.wrapContentHeight(Alignment.CenterVertically),
textAlign = TextAlign.Center
)
}
Divider(color = Color.LightGray, thickness = Dp.Hairline)
LazyColumn(
Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
val sortedList = PopularBooksList.sortedWith(comparator)
items(sortedList, key = { it.title }) {
Row(
Modifier.animateItemPlacement()
.height(IntrinsicSize.Max),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
it.title,
Modifier.weight(5f)
.fillMaxHeight()
.padding(4.dp)
.wrapContentHeight(Alignment.CenterVertically),
textAlign = TextAlign.Center
)
Text(
it.author,
Modifier.weight(2f)
.fillMaxHeight()
.padding(4.dp)
.wrapContentHeight(Alignment.CenterVertically),
textAlign = TextAlign.Center
)
Text(
"${it.published}",
Modifier.width(55.dp)
.fillMaxHeight()
.padding(4.dp)
.wrapContentHeight(Alignment.CenterVertically),
textAlign = TextAlign.Center
)
Text(
"${it.salesInMillions}",
Modifier.width(65.dp)
.fillMaxHeight()
.padding(4.dp)
.wrapContentHeight(Alignment.CenterVertically),
textAlign = TextAlign.Center
)
}
}
}
}
}
}
private val TitleComparator = Comparator<Book> { left, right ->
left.title.compareTo(right.title)
}
private val AuthorComparator = Comparator<Book> { left, right ->
left.author.compareTo(right.author)
}
private val YearComparator = Comparator<Book> { left, right ->
right.published.compareTo(left.published)
}
private val SalesComparator = Comparator<Book> { left, right ->
right.salesInMillions.compareTo(left.salesInMillions)
}
private val PopularBooksList = listOf(
Book("The Hobbit", "J. R. R. Tolkien", 1937, 140),
Book("Harry Potter and the Philosopher's Stone", "J. K. Rowling", 1997, 120),
Book("Dream of the Red Chamber", "Cao Xueqin", 1800, 100),
Book("And Then There Were None", "Agatha Christie", 1939, 100),
Book("The Little Prince", "Antoine de Saint-Exupéry", 1943, 100),
Book("The Lion, the Witch and the Wardrobe", "C. S. Lewis", 1950, 85),
Book("The Adventures of Pinocchio", "Carlo Collodi", 1881, 80),
Book("The Da Vinci Code", "Dan Brown", 2003, 80),
Book("Harry Potter and the Chamber of Secrets", "J. K. Rowling", 1998, 77),
Book("The Alchemist", "Paulo Coelho", 1988, 65),
Book("Harry Potter and the Prisoner of Azkaban", "J. K. Rowling", 1999, 65),
Book("Harry Potter and the Goblet of Fire", "J. K. Rowling", 2000, 65),
Book("Harry Potter and the Order of the Phoenix", "J. K. Rowling", 2003, 65)
)
private class Book(
val title: String,
val author: String,
val published: Int,
val salesInMillions: Int
)
You have tons of codes and I only see lists, so its hard to guess what's going on, but based on
the list of breweries does not sort
I would advice creating a very simple working app with 1 data class 1 Viewmodel 1 Screen with a LazyColumn and 1 Button.
Consider this:
Your data class
data class Person(
val id: Int,
val name: String
)
Your ViewModel
class MyViewModel {
val peopleList = mutableStateListOf<Person>() // SnapshotStateList
fun onSortButtonClicked() {
// do your sorting logic here
// update your mutable`State`List
}
}
Your Composable Screen
#Composable // I just put the view model as an argument, don't follow it you don't need to
fun MyScreen(viewModel: MyViewModel) {
val myList = viewModel.peopleList
LazyColumn {
items(items = myList, key = { person -> person.id }) { person ->
//.. your item composable //
}
Button(
onClick = { viewModel.onSortButtonClicked() }
)
}
}
It works but its not advisable to use MutableList inside a State or mutableState as any changes you make on its elements won't trigger re-composition, your best way to do it is to manually copy the entire List and create a new one which is again not advisable, thats why SnapshotStateList<T> or mutableStateListOf<T> is not only recommended but the only way to deal with lists in Jetpack Compose development efficiently.
Using SnapshotStateList, any updates (in my experience) such as deleting an element, modifying by doing .copy(..) and re-inserting to the same index will guarantee a re-composition on a Composable that reads that element, in the case of the "working sample code", if I change the name of a person in the SnapshotStateList like peopleList[index] = person.copy(name="Mr Person"), the item composable that reads that person object will re-compose. As for your Sorting problem, I haven't tried it yet so I'm not sure, but I think the list will sort if you simply perform a sorting operation in the SnapshotStateList.
Take everything I said with the grain of salt, though I'm confident with the usage of the SnapshotStateList for the "working sample code", however there are tons of nuances and things happening under the hood that I'm careful not to just simply throw around, but as you said
I am new at this
So am I. I'm confident this is a good starting point dealing with collections/list in jetpack compose.

Row composable not taking up the full width of parent

I have a shopping list item composable that is not taking up the entire width of the parent, as you can see below with the red border. I want it to be flush against the parent's edge. And why is there some space or padding just before the checkbox? What needs to be modified?
Composable
#Composable
fun ShoppingListScreenItem(
itemName: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
Row(
modifier = Modifier.fillMaxWidth().border(2.dp, Red),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
modifier = Modifier.padding(0.dp),
checked = checked,
onCheckedChange = onCheckedChange
)
Text(
modifier = Modifier.padding(start = 8.dp),
fontWeight = FontWeight.Bold,
text = itemName
)
}
}
Parent Composable
#Composable
fun ShoppingListScreen(
navController: NavHostController,
shoppingListScreenViewModel: ShoppingListScreenViewModel
) {
val scope = rememberCoroutineScope()
val name = stringResource(id = R.string.item_name)
val nameError = stringResource(id = R.string.item_IsNameError)
val category = stringResource(id = R.string.item_category)
val categoryError = stringResource(id = R.string.item_IsCategoryError)
val focusManager = LocalFocusManager.current
val allItems =
shoppingListScreenViewModel.shoppingListItemsState.value?.collectAsLazyPagingItems()
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = "AppBar") },
backgroundColor = Color.White,
navigationIcon = if (navController.previousBackStackEntry != null) {
{
IconButton(onClick = { navController.navigateUp() }) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Back"
)
}
}
} else {
null
}
)
},
floatingActionButton = {
FloatingActionButton(
onClick = {
shoppingListScreenViewModel.setStateValue("ShowAddItemDialog", true)
},
backgroundColor = Color.Blue,
contentColor = Color.White
) {
Icon(Icons.Filled.Add, "")
}
},
// Defaults to false
isFloatingActionButtonDocked = false,
bottomBar = { BottomNavigationBar(navController = navController) }
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
LazyColumn(
contentPadding = PaddingValues(
vertical = 8.dp,
horizontal = 8.dp
)
) {
//todo change it to non null
items(allItems!!) { item ->
ShoppingListScreenItem(
itemName = item?.name!!,
checked = item.isInCart
) { isChecked ->
scope.launch {
shoppingListScreenViewModel.changeItemChecked(item, isChecked)
}
}
}
}
if (shoppingListScreenViewModel.shoppingListScreenState.value.showAddItemDialog) {
OnTheFlyAddItemDialog(
shoppingListScreenViewModel = shoppingListScreenViewModel,
focusManager = focusManager,
navController = navController,
onDismiss = {
shoppingListScreenViewModel.setStateValue(name, "")
shoppingListScreenViewModel.setStateValue(nameError, false)
shoppingListScreenViewModel.setStateValue(category, "")
shoppingListScreenViewModel.setStateValue(categoryError, false)
shoppingListScreenViewModel.setStateValue("ShowAddItemDialog", false)
}
)
{
scope.launch {
shoppingListScreenViewModel.addShoppingListItemToDb()
shoppingListScreenViewModel.setStateValue("ShowAddItemDialog", false)
shoppingListScreenViewModel.setStateValue(name, "")
shoppingListScreenViewModel.setStateValue(nameError, false)
shoppingListScreenViewModel.setStateValue(category, "")
shoppingListScreenViewModel.setStateValue(categoryError, false)
// triggerCount++
}
}
}
Button(
modifier = Modifier.padding(vertical = 24.dp),
onClick = {
navController.navigate(NavScreens.AddItemScreen.route) {
popUpTo(NavScreens.AddItemScreen.route) {
inclusive = true
}
}
}
) {
Text("Goto add item screen")
}
}
}
}
You set a contentPadding in your LazyColumn that is responsible for the spaces. Remove it, or set it to zero.
LazyColumn(
contentPadding = PaddingValues(
vertical = 8.dp,
horizontal = 0.dp
)
)
The padding around the Checkbox depends on the minimumTouchTargetSize defined when the onClick is not null with a hardcoded value of 48.dp.
You can override it using:
CompositionLocalProvider(
LocalMinimumTouchTargetEnforcement provides false,
) {
Checkbox(
modifier = Modifier.padding(0.dp), //4.dp
checked = false,
onCheckedChange = {}
)
}
but it is not the best choice in term of accessibility.
.
The space around the Row depends on the contentPadding defined in the LazyColumn.

dynamically change text in cards - Jetpack Compose state

New to Compose and struggling hard with more complex state cases.
I cant seem to change the text dynamically in these cards, only set the text manually. Each button press should change the text in a box, starting left to right, leaving the next boxes empty.
What is wrong here?
UI:
val viewModel = HomeViewModel()
val guessArray = viewModel.guessArray
#Composable
fun HomeScreen() {
Column(
modifier = Modifier
.fillMaxWidth()
) {
WordGrid()
Keyboard()
}
}
#Composable
fun WordGrid() {
CardRow()
}
#Composable
fun Keyboard() {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
MyKeyboardButton(text = "A", 35)
MyKeyboardButton(text = "B", 35)
}
}
#Composable
fun MyCard(text: String) {
Card(
modifier = Modifier
.padding(4.dp, 8.dp)
.height(55.dp)
.aspectRatio(1f),
backgroundColor = Color.White,
border = BorderStroke(2.dp, Color.Black),
) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(
text = text,
fontSize = 20.sp,
)
}
}
}
#Composable
fun CardRow() {
guessArray.forEach { rowCards ->
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
rowCards.forEach {
MyCard(it)
println(it)
}
}
}
}
#Composable
fun MyKeyboardButton(text: String, width: Int) {
Button(
onClick = {
guessArray[viewModel.currentRow][viewModel.column] = text
viewModel.column += 1
},
modifier = Modifier
.width(width.dp)
.height(60.dp)
.padding(0.dp, 2.dp)
) {
Text(text = text, textAlign = TextAlign.Center)
}
}
ViewModel:
class HomeViewModel : ViewModel() {
var currentRow = 0
var guessArray = Array(5) { Array(6) { "" }.toMutableList() }
var column = 0
}
The grid is created, but the text is never changed.
To make Compose view recompose with the new value, a mutable state should be used.
You're already using it with remember in your composable, but it also needs to be used in your view model for properties, which should trigger recomposition.
class HomeViewModel : ViewModel() {
var currentRow = 0
val guessArray = List(5) { List(6) { "" }.toMutableStateList() }
var column = 0
}

Categories

Resources