I'm using SelectionTracker form the support-library-v28
It works great, just as expected.
Only thing I need, is to enable Select All feature (using the ToolBar)
Looking at the API, I see that there is one way to select all, but that one requires creating Iterable<Long> with all the values, meaning, create an array which hold Long values from 1 to datasource.size()
Is there any simpler way to select all the items in my datasource?
Seeing as I've recently had to set up the same sort of functionality, I thought I'd share my approach. Using the setItemsSelected(Iterable<K> keys, boolean selected) method really isn't as complex as it seems.
Yes, you will need to pass in an iterable. What I did was, loop through my data and store the index of each item as a 'long' inside of an arrayList().
EX:
yourData.forEachWithIndex { i, item ->
//Be sure to start at one, just plus one
someOtherTempArray.add(i.toLong() + 1)
}
Then I created a method that makes it easy to 'trigger' the select all functionality:
EX:
private fun startHandler(isChecked: Boolean){
val handler = android.os.Handler(Looper.getMainLooper())
val runnable = Runnable {
kotlin.run {
mTracker!!.setItemsSelected(someOtherTempArray.asIterable(),
isChecked)
}
}
handler.post(runnable)
}
The 'isChecked' parameter, which will essentially determine if we want to select all or deselect all.
Note that I am simply using my arrayList of 'keys' (for me this is just the index of the item. However, this may differ depending on how you have your ItemDetails Builder set up) and calling the Kotlin .asIterable() function to turn it into an iterable.
I have also placed the call to setItemsSelected() inside of a handler to force the selection to take place on the UI thread.
Now whenever you want to select/deselect all, you can call the startHandler(true)!
Related
I already have a list adapter that works properly. But I want to divide the object in the list into sections according to the date they were created. Something like this:
I found something called "sectioned recycler view" but couldn't find any documentation on that. I read all the related questions, but they all are either outdated or use a third-party library. What's the native way of implementing this feature?
There are a couple of approaches you could use. First the easy one:
make the header part of your item layout, but with GONE visibility by default
in onBindViewHolder, decide whether the header should be VISIBLE or GONE
The logic there depends on what you want, but it could be as simple as
val visible = position == 0 || items[position].date != items[position - 1].date
Basically you just need to work out what the condition is that would cause an item to be in a different "group" than the previous item, and then if it's met, show the header over that item.
The approach MarkL is talking about is more complex, but it's also more robust - by having separate Item and Header elements, you can treat them differently, and even do stuff like having the header collapse/show its children, select them all etc. You can do that with the other approach, but it requires more code since you're not really treating things as groups, it's more of a trick when it comes to displaying stuff.
Basically, ignoring the how for now, you need a list of headers and items. A sealed class is a good way to represent that:
sealed class ListElement {
data class Header(val date: Date) : ListElement()
data class Item(val itemData: YourItem) : ListElement()
}
I've made Item a wrapper class that holds your actual data object inside, since that's probably coming from elsewhere and you can't define it as part of this sealed class hierarchy - so sticking it inside one of the subclasses like this allows you to do that.
So now you can have a List<ListElement> containing Headers and Items in display order. Since you've mentioned creating the ViewHolders in a comment I won't explain all that, but basically when you're getting the item type for a position, you just need to check is Header or is Item and then handle it from there.
As for creating that list, there are lots of ways to do it - you could use groupBy to generate a Map of dates to lists of items, map each of those entries to a list of Header, Item, Item..., and flatten the whole thing into a single list:
items.map { Item(it) }
.groupBy { it.itemData.date }
.entries
.flatMap { (date, items) -> listOf(Header(date)) + items }
The advantage with a map like this is it's an actual grouped structure, so you can keep it around to generate flat lists for display - e.g. hiding a group's contents by only including the header in the list.
Or you could just build the list yourself, similar to the logic I mentioned in the first example - if the date has changed from the previous item, insert a Header first:
val list = mutableListOf<ListElement>().apply {
for (item in items) {
// add a header if the date changed - this handles the first header
// in an empty list too (where lastOrNull returns null, so the date is null)
val previousItemDate = (lastOrNull() as? Item)?.itemData?.date
if (previousItemDate != item.date) add(Header(item.date))
add(Item(item))
}
}
Or you could use fold. Don't forget to sort stuff!
You could create 2 types of view holders:
header which holds the date
data container which holds the other information.
And then create a list of objects which contain something like this:
listToBind = (header, data, data, header, data, data)
For your case, where header & info is the same object, you can do something like this:
take your object you receive from backend (example)
YourObject(val header: String, val info:InfoObject)
create 2 display objects, both inheriting from a type that your adapter accepts (say - AdapterDisplayEntity)
HeaderDisplayEntity(val header: String): AdapterDisplayEntity
InfoDisplayEntity(val info: InfoObject): AdapterDisplayEntity
now you can use your list that contains these items and submit to your adapter.
Use nested recycler view for this instead. You can check the example here.
Best solution for this scenario so far.
If you are using Jetpack Compose you can use the stickyHeader() as documented in the documentation
I've been messing around with Jetpack Compose and currently looking at different ways of creating/managing/updating State.
The full code I'm referencing is on my github
I have made a list a piece of state 3 different ways and noticed differences in behavior. When the first list button is pressed, it causes all 3 buttons to be recomposed. When either of the other 2 lists are clicked though they log that the list has changed size, update their UI but trigger no recompose of the buttons ?
To clarify my question, why is that when I press the button for the firsList I get the following log messages, along with size updates:
Drawing first DO list button
Drawing List button
Drawing second DO list button
Drawing List button
Drawing third DO list button
Drawing List button
But when I press the buttons for the other 2 lists I only get the size update log messages ?
Size of list is now: 2
Size of list is now: 2
var firstList by remember{mutableStateOf(listOf("a"))}
val secondList: SnapshotStateList<String> = remember{ mutableStateListOf("a") }
val thirdList: MutableList<String> = remember{mutableStateListOf("a")}
Row(...) {
println("Drawing first DO list button")
ListButton(list = firstList){
firstList = firstList.plus("b")
}
println("Drawing second DO list button")
ListButton(list = secondList){
secondList.add("b")
}
println("Drawing third DO list button")
ListButton(list = thirdList){
thirdList.add("b")
}
}
When I click the button, it adds to the list and displays a value. I log what is being re-composed to help see what is happening.
#Composable
fun ListButton(modifier: Modifier = Modifier,list: List<String>, add: () -> Unit) {
println("Drawing List button")
Button(...,
onClick = {
add()
println("Size of list is now: ${list.size}")
}) {
Column(...) {
Text(text = "List button !")
Text(text = AllAboutStateUtil.alphabet[list.size-1])
}
}
}
I'd appreciate if someone could point me at the right area to look so I can understand this. Thank you for taking the time.
I'm no expert (Well,), but this clearly related to the mutability of the lists in concern. You see, Kotlin treats mutable and immutable lists differently (the reason why ListOf<T> offers no add/delete methods), which means they fundamentally differ in their functionality.
In your first case, your are using the immutable listOf(), which once created, cannot be modified. So, the plus must technically be creating a new list under the hood.
Now, since you are declaring the immutable list in the scope of the parent Composable, when you call plus on it, a new list is created, triggering recompositions in the entire Composable. This is because, as mentioned earlier, you are reading the variable inside the parent Composable's scope, which makes Compose figure that the entire Composable needs to reflect changes in that list object. Hence, the recompositions.
On the other hand, the type of list you use in the other two approaches is a SnapshotStateList<T>, specifically designed for list operations in Compose. Now, when you call its add, or other methods that alter its contents, a new object is not created, but a recomposition signal is sent out (this is not literal, just a way for you to understand). The way internals of recomposition work, SnapshotStateList<T> is designed to only trigger recompositions when an actual content-altering operation takes place, AND when some Composable is reading it's content. Hence, the only place where it triggered a recomposition was the list button that was reading the list size, for logging purposes.
In short, first approach triggers complete recompositions since it uses an immutable list which is re-created upon modification and hence the entire Composable is notified that something it is reading has changed. On the other hand, the other two approaches use the "correct" type of lists, making them behave as expected, i.e., only the direct readers of their CONTENT are notified, and that too, when the content (elements of the list) actually changes.
Clear?
EDIT:
EXPLANATION/CORRECTION OF BELOW PROPOSED THEORIES:
You didn't mention MutableListDos in your code, but I'm guessing it is the direct parent of the code you provided. So, no, your theory is not entirely correct, as in the immutable list is not being read in the lambda (only), but the moment and the exact scope where you are declaring it, you send the message that this value is being read then and there. Hence, even if you removed the lambda (and modified it from somewhere else somehow), it will still trigger the recompositions. The Row still does have a Composable scope, i.e., it is well able to undergo independent recompositions, but the variable itself is being declared (and hence read) in the parent Composable, outside the scope of the Row, it causes a recomp on the entire parent, not just the Row Composable.
I hope we're clear now.
I have a Flow of PagingData and I need to check if this flow contains specific Item value.
I know that I cant collect it by .collectAsLazyPagingItems().itemSnapshotList.items, but it is possible only in #Composable. How can I do it or though get the List of Item?
I have tried like this:
viewModelScope.launch {
itemsFlow.onEach {
it.map { item ->
if (item == myItem) {
//do something
}
}
}.collect()
}
but it does nothing.
PagingData does not hold any state, it's only used to hook up paging to something that can actually collect and present the state, like PagingDataAdapter or in LazyPagingItems using the method you've already found.
Another way to think about it is that it is impossible to inspect whether a paginated list contains an item, without first loading all the items, and currently the only way to realize the state in Paging is by collecting it in the UI layer.
Similarly, PagingData.map does not eagerly load the entire list, it operates incrementally as items are loaded in to be displayed in the UI. So you can use it to check if / when a specific item is loaded, but you cannot use it to search the list since it only runs as items are loaded for the UI.
I comes from Java background and working first time on Kotlin. For most of the people it will be basic question, but it may help people who start working first time on Kotlin and comes from Java background
So, let say I have listadapter and I want to set list of item in that. I have two options now.
1) create a private property which stores list of items and then create a setter for it, which set the list and call notifydatasetChanged()
2) create a property with set property function and then access like instance.property
Which will be better option in Kotlin out of above two options.
A property in Kotlin is nothing else then a getter and setter for a value. If you don't want to provide a getter, you have to use a fun setData(data: List).
Otherwise it's also possible to handle everything with the property
var data = listOf()
set(data: List) {
field = data
notifydatasetChanged()
}
But eventually it's even better to use an implementation with DiffUtil.
I have an AutoCompleteTextView that is filled with cities from an sqlite database that calls an AsyncTask on item click, recently I added an option to detect my location using the gps, so the problem is I can detect the city (i.e Beirut) and set the text for the AutoCompleteTextView but the thing is that the dropdown filter opens showing Beirut (which is correct) but I still need to click on the list item to invoke the listener, how to do so programmatically
How to:
Enter the Activity (DONE)
Detect location (DONE)
set text of text view (DONE)
show textview dropdown list(DONE)
choose the item that will be returned, since it will only return one city (NOT DONE)
To be clear, Tano's solution is sufficient to answer this question. But, in case others run into the same use case I did, here's some more background that may potentially help you...
I had been running into this issue specifically while trying to make a non-editable Material Exposed Dropdown Menu and set it's initial value programmatically. The documentation to create this type of "dropdown" can be found in the Exposed Dropdown Menus section here, which suggests a mechanism using TextInputLayout and AutocompleteTextView (even if you don't want autocomplete functionality).
Failed Solution 1:
At first glance setListSelection() and getListSelection() seemed like they might do the trick. But after many trials, I learned that they may not be sufficient because they only work when the list popup isShowing(). So for example, if you simply want to set the initial selection without having to show the list popup first, this will not work.
Failed Solution 2:
Then I tried setText() which showed the proper text in my textbox. Yay! But wait! When I clicked on the text view, only a subset of options in the list popup were shown for some reason. Why was that? The key thing to keep in mind here is that since this is an autocomplete textview, it by default filters out options based off of the text in the textview. This might not be apparent, especially if you're solely using this control for the sake of making a simple non-editable dropdown selector.
Solution:
This brings us to our actual solution (suggested by Tano)... setText() with filter as false will turn off the filtering capabilities AND it will not change the contents of your list popup.
autoCompleteTextView.setText(myText, false);
I was facing a similar problem and this solved my issue. Important is to call setText(<text>, <filter boolean>) in order not to filter with the given text set the second parameter with false. The text will be got from the dropdown adapter.
Solution snippet:
automCompleteTextView.setText(automCompleteTextView.getAdapter().getItem(position).toString(), false);
A solution were you don't need to change your API level.
automCompleteTextView.setAdapter(adapter);
// set default selection, filtering is active so all items is not visible in drop-down menu
automCompleteTextView.setText(automCompleteTextView.getAdapter().getItem(0).toString());
// change filtering for the adapter so all items can be visible in drop-down menu
adapter.getFilter().filter(null);
one-liner for the same job but requires higher API level
automCompleteTextView.setText(automCompleteTextView.getAdapter().getItem(0).toString(), false);
I figure out after dig into the AutoCompleteTextView code on android source code:
fun AutoCompleteTextView.selectItem(text: String, position: Int = 0) {
this.setText(text)
this.showDropDown()
this.setSelection(position)
this.listSelection = position
this.performCompletion()
}
autoComplete.setListSelection(position);
I have used autoCompleteTextView.setText(myText, false); solution as well, however it sometimes failed. I mean it was actively filtering results so, when user clicks there was only 1 item at dropdown.
In addition I also needed this to work on custom objects as well, and this is my my solution:
binding.hourEditText.configureDropDownMenu(viewModel.hours) { it.hourString() }
.subscribe {
// Do whatever you need when on click.
}
.addTo(disposables)
fun <T> AutoCompleteTextView.configureDropDownMenu(list: List<T>, toString: ((T) -> String)? = null): Observable<T> {
keyListener = null
val textItems = toString?.let(list::map) ?: list.map { it.toString() }
setAdapter(NonFilterArrayAdapter(context!!, android.R.layout.simple_spinner_dropdown_item, textItems))
return itemClickEvents().map {
list[it.position]
}
}
private class NonFilterArrayAdapter<T>(context: Context, #LayoutRes resource: Int, objects: List<T>) : ArrayAdapter<T>(context, resource, objects) {
override fun getFilter() = NonFilter()
private class NonFilter : Filter() {
override fun performFiltering(constraint: CharSequence?) = FilterResults()
override fun publishResults(constraint: CharSequence?, results: FilterResults?) = Unit
}
}
Note: This also contains a bit of Rx, but it can be removed easily.
Try with adding below after setText() in AutoCompleteTextview:-
autoCompleteTV.setSelection(position);
Updated:
This will work in Spinner and AutoCompleteTextView which has dropdown feature, but it will not work with EditText.
Here you can check docs for AbsSpinner in this link:
https://developer.android.com/reference/android/widget/AbsSpinner.html#setSelection(int)
The problem is that you are setting a text and the AutoCompleteTextView is only showing words that match with that text. A non elegant way of solving this is to set an high threshold (at least the max length of the names of the cities) to force Android to show you all the values of your list (this threshold is the number of characters that the field must have to search similarities).
Using the Nilton Vasques solution it can be so:
with(autoComplete) {
setAdapter(this#YourFragment.adapter)
setText(itemText)
showDropDown()
listSelection = if (itemIndex > 0) itemIndex - 1 else 0 // Because AutoCompleteTextView shows the next row.
performCompletion()
}
Notice, that it will show a drop-down list, otherwise listSelection won't work. If you call dismissDropDown(), the item won't be selected. If you don't want to show the drop-down list, you can use setOnTouchListener to capture opening the list, but it hardly will help (you should resolve a filtering problem).
setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
showDropDown()
listSelection = if (itemIndex > 0) itemIndex - 1 else 0
performCompletion()
requestFocus()
}
false
}