Android - How to load several lists from API? - android

I have an API (https://www.thecocktaildb.com/api.php) I want to load all lists one by one. There's a request where I can find all categories and the only difference beetween lists in the filter in URL.request function What should i use to achieve such functionality? The sketch of how it should be. Maybe Paging library can be useful? Please help!

I believe that you can use Paging 3 for that.
I'll assume that you will start this by having all the categories in a list.
class DrinkSource(
private val categories: List<String>
) : PagingSource<String, Drink>() {
override suspend fun load(
params: LoadParams<String>
): LoadResult<String, Drink> {
val result = requestFromAPI(params.key ?: categories[0])
val index = categories.indexOf(params.key)
val previous = if (index == 0) null else categories[index - 1]
val next = if (index == categories.size - 1) null else categories[index + 1]
return LoadResult.Page(result, prevKey = previous, nextKey = next)
}
private suspend fun requestFromAPI(category: String): List<Drink> {
// replace this with an API call
return listOf(Drink(1, ""))
}
}
From what i saw of your API there were no pagination in the category query, so this solution would work.

Related

Can't understand code of getRefreshKey in Android paging3 codelab

// GitHub page API is 1 based: https://developer.github.com/v3/#pagination
private const val GITHUB_STARTING_PAGE_INDEX = 1
class GithubPagingSource(
private val service: GithubService,
private val query: String
) : PagingSource<Int, Repo>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repo> {
val position = params.key ?: GITHUB_STARTING_PAGE_INDEX
val apiQuery = query + IN_QUALIFIER
return try {
val response = service.searchRepos(apiQuery, position, params.loadSize)
val repos = response.items
val nextKey = if (repos.isEmpty()) {
null
} else {
// initial load size = 3 * NETWORK_PAGE_SIZE
// ensure we're not requesting duplicating items, at the 2nd request
position + (params.loadSize / NETWORK_PAGE_SIZE)
}
LoadResult.Page(
data = repos,
prevKey = if (position == GITHUB_STARTING_PAGE_INDEX) null else position - 1,
nextKey = nextKey
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
} catch (exception: HttpException) {
return LoadResult.Error(exception)
}
}
// The refresh key is used for subsequent refresh calls to PagingSource.load after the initial load
override fun getRefreshKey(state: PagingState<Int, Repo>): Int? {
// We need to get the previous key (or next key if previous is null) of the page
// that was closest to the most recently accessed index.
// Anchor position is the most recently accessed index
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}
}
This is the code of getRefreshKey function of the paging3 codelab.
I thought that it's just okay to return state.anchorPosition. But why is this returning closestPagetToPosition's previous key plus 1??
This is the link of the paging 3 code lab.
Well, state.anchorPosition is index of the first item you see in this list, getRefreshKey should return the key to load on refresh.
In this example, key is index of page and each page typically contains many items, lets say 20. Now loading two pages would mean using keys 1 and 2 and having 40 items loaded.
In this situation, when you are looking at item on index 30, state.anchorPosition is 30 as well, but you definitely don't want your refresh key to be 30, you need it to be 2 because item on index 30 comes from page 2. This is what closestPageToPosition does.

RecycleView sorting

For some time I have been struggling with the problem of sorting information in RecycleView, the data comes from Firebase, I would like to sort single row depending on one of the objects from a class. maybe the code will explain it better.
private fun fetchHours(){
var link = FirebaseAuth.getInstance().uid
val ref = FirebaseDatabase.getInstance().getReference("/WorkTime/$link")
ref.addListenerForSingleValueEvent(object: ValueEventListener{
override fun onDataChange(p0: DataSnapshot) {
val adapter = GroupAdapter<GroupieViewHolder>()
p0.children.forEach {
Log.d("Vievactivity", it.toString())
var singlerow = it.getValue(CounterActivity.WorkTimeCountedClass::class.java)
if (singlerow != null ){
adapter.add(WorkTimeList(singlerow))
}
}
recycleViev_list.adapter = adapter
adapter.setOnItemClickListener{ item, view ->
val useruuid = item as WorkTimeList
val intent = Intent(view.context, EditDataActivity::class.java)
intent.putExtra(CLICKED_ITEM_KEY, useruuid.naz.uuid)
startActivity(intent)
}
}
override fun onCancelled(p0: DatabaseError) {
}
})
}
}
class WorkTimeList(var naz: CounterActivity.WorkTimeCountedClass): Item<GroupieViewHolder>(){
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.itemView.row_viewactivity_invisible_uuid.text = naz.uuid
viewHolder.itemView.Row_viewactivity_employer.text = naz.employer
viewHolder.itemView.Row_viewactivity_StartWork.text = naz.startTimeString
viewHolder.itemView.Row_viewactivity_StopWork.text = naz.stopTimeString
viewHolder.itemView.imageView_planTime.text = naz.planedTimeToSpendAtWorkString
viewHolder.itemView.imageView_complitedTime.text = naz.totalTimeSpendAtworkString
viewHolder.itemView.imageView_overHours.text = naz.overHoursString
}
override fun getLayout(): Int {
return R.layout.row_viewactivity
}
}
as a result, he gets something like that
screen
I would like individual rows to be sorted depending on user preference, for example ascending from the variable viewHolder.itemView.Row_viewactivity_StartPracy.text = naz.startTimeString or descending from viewHolder.itemView.imageView_complitedTime.text = name. TotalTime Does anyone have an idea how to solve this problem?
val ref = FirebaseDatabase.getInstance().getReference("/WorkTime/$link")
ref.orderByChild("order").addListenerForSingleValueEvent(object: ValueEventListener{
override fun onDataChange(p0: DataSnapshot) {
val adapter = GroupAdapter<GroupieViewHolder>()
p0.children.forEach {
Log.d("Vievactivity", it.toString())
var singlerow = it.getValue(CounterActivity.WorkTimeCountedClass::class.java)
if (singlerow != null ){
adapter.add(WorkTimeList(singlerow))
}
}
recycleViev_lista.adapter = adapter
I found a solution, maybe just writing the first question here inspired me :)
So using "orderByChild" I added sorting, unfortunately only increasing sorting works. To change this I came up with the idea that the value after which I want to sort subtract from 0 - it will reverse the sorting method :)
I am not good at explaining, so maybe for example:
Writes to the Firebase start time in milliseconds for example (1579097163159), (1578374795583), (1579152252463) - sorting this data we get the result in increasing
1 - (1578374795583)
2 - (1579097163159)
3 - (1579152252463)
Unfortunately, I did not find how to change orderByChaild tabs so that they sort in descending order ...... we can subtract from these values ​​and get the expected effect:
1 - (1578374795583) -> 0 - 1578374795583 = - 1578374795583
2 - (1579097163159) -> 0 - 1579097163159 = - 1579097163159
3 - (1579152252463) -> 0 - 1579152252463 = - 1579152252463
We need to add a variable to Firebase, which will be the result of this operation, we then sort by the result of this operation and it's ready.
1 - 1579152252463
2 - 1579097163159
3 - 1578374795583
I hope I helped someone as much as I can't count how many times I found help here. Regards

How to filter efficiently with optional params in Kotlin?

I am working on Kotlin list filter. Let's say I have a list of Worker instances and I want to design a function which filters the list on condition for startAge and maxSalary nullable params.
Data class
data class Worker(name:String?, age: Int?, salary: Int?){
}
Function filteredList()
fun filterList(original: List<Worker>, startAge: Int?, maxSalary: Int?): List<Student>{
val list = original.filter{
it.age >= startAge && it.salary <= maxSalary
}
return list
}
At that point, I want to design the filterList function in such a way if any param is passed as a null, that condition should not be skipped. How do I do that? For example, calling with
val list = filterList(original, null, 100) should return list of workers whose max salary is 100 but does not matter the age.
My thought
In this particular problem I can choose default startAge = Int.MAX_VALUE and maxSalary = Int.MIN_VALUE when null is passed in params. But I think there can be many kind of conditional check inside filter (i.e compare dates, string equality check, evaluate expressions etc.) which might not always be possible to cover with default value. How can I get rid of this glitch?
You could create a helper function isNullOr to check for null or invoke a predicate otherwise:
inline infix fun <T> T?.isNullOr(predicate: (T) -> Boolean): Boolean = if (this != null) predicate(this) else true
With this function your filter statement will look like this:
fun filterList(original: List<Worker>, startAge: Int?, maxSalary: Int?): List<Worker> {
val list = original.filter { worker ->
startAge isNullOr { worker.age >= it } && maxSalary isNullOr { worker.salary <= it }
}
return list
}
You can use the Elvis operator ?: to evaluate the age or the salary to a ridiculous value (Int.MIN_VALUE or Int.MAX_VALUE) if the parameter is null.
it.age >= (startAge ?: Int.MIN_VALUE) && it.salary <= (maxSalary ?: Int.MAX_VALUE)
If say startAge is null, then it.age will be compared to Int.MIN_VALUE, which will always be true. Same goes for maxSalary.
But you also should decide what happens when it.age or it.salary is null, which you did not mention in the question.
Edit:
If you don't have default values, you can use ?.let to transform the optional params to optional booleans, then use ?: to default them to true:
fun filterList(original: List<Worker>, startAge: Int?, maxSalary: Int?): List<Worker>{
val list = original.filter{
(startAge?.let { x -> it.age >= x } ?: true) && (maxSalary?.let { x -> it.salary <= x } ?: true)
}
return list
}

Get uncommon elements from two list - KOTLIN

I have two list of same model class (STUDENT), sample student object structure is given below,
{
"_id": "5a66d78690429a1d897a91ed",
"division": "G",
"standard": "X",
"section": "Secondary",
"lastName": "Sawant",
"middleName": "Sandeep",
"firstName": "Shraddha",
"pin": 12345,
"isEditable": true,
"isTracked": false
}
One list have 3 objects and other 2. lets say, List A has 1, 2, 3 students and List B has 1, 2
So my question is there any inbuilt functions to get the uncommon element by comparing just the id? If not how can i solve this issue.
FYI, following are the two approaches i have made to solve, but failed miserably.
Approach 1.
internal fun getDistinctStudents(studentsList: List<Students>, prefStudents: List<Students>): List<Students> {
val consolidated = prefStudents.filter {
prefStudents.any { students: Students -> it._id == students._id }
}
return prefStudents.minus(consolidated)
}
Approach 2.
internal fun getDistinctStudents(studentsList: List<Students>, prefStudents: List<Students>): List<Students> {
val consolidatedStudents = studentsList + prefStudents
val distinctStudents = consolidatedStudents.distinctBy{ it._id }
return prefStudents.minus(distinctStudents)
}
Any kind of help will be greatly appreciated.
Thanks
A more Kotlin way to achieve what Ahmed Hegazy posted. The map will contain a list of elements, rather than a key and count.
Using HashMap and Kotlin built-ins. groupBy creates a Map with a key as defined in the Lambda (id in this case), and a List of the items (List for this scenario)
Then filtering out entries that have a list size other than 1.
And finally, converting it to a single List of Students (hence the flatMap call)
val list1 = listOf(Student("1", "name1"), Student("2", "name2"))
val list2 = listOf(Student("1", "name1"), Student("2", "name2"), Student("3", "name2"))
val sum = list1 + list2
return sum.groupBy { it.id }
.filter { it.value.size == 1 }
.flatMap { it.value }
I know that this is an old post but I believe there is a neater and shorter solution. See sample below using Mikezx6r's data whose answer was accepted above.
val list1 = listOf(Student("1", "name1"), Student("2", "name2"))
val list2 = listOf(Student("1", "name1"), Student("2", "name2"), Student("3", "name2"))
val difference = list2.toSet().minus(list1.toSet())
If you have two lists, where element is identified e.g. by some kind of id (item.id), then you can do as below:
fisrtList.filter { it.id !in secondList.map { item -> item.id } }
I assume firstList and secondList contain objects of the same type naturally.
Here's an extension function that basically does what you want. It makes an assumption that the element E knows how to be identified, e.g. by Student._id in your example:
infix fun <E> Collection<E>.symmetricDifference(other: Collection<E>): Set<E> {
val left = this subtract other
val right = other subtract this
return left union right
}
Here's an example of how it could be used:
val disjunctiveUnion: List<Student> = listA symmetricDifference listB
An example test case I'd written for it:
#Test
fun `symmetric difference with one of either set`() {
val left = listOf(1, 2, 3)
val right = listOf(2, 3, 4)
val result = left symmetricDifference right
assertEquals(setOf(1, 4), result)
}
This is the solution using a HashMap, the code could be better, but I'm very new to kotlin
fun getDistinctStudents(studentsList: List<Student>, prefStudents: List<Student>): List<Student> {
val studentsOccurrences = HashMap<Student, Int>()
val consolidatedStudents = studentsList + prefStudents
for (student in consolidatedStudents) {
val numberOfOccurrences = studentsOccurrences[student]
studentsOccurrences.put(student, if(numberOfOccurrences == null) 1 else numberOfOccurrences + 1)
}
return consolidatedStudents.filter { student -> studentsOccurrences[student] == 1 }
}
Your student class should be a data class or at least overrides hashcode and equals to be used as a key.
Until someone comes up with a neater and shorter solution, here's a working one that I think is easy enough to read:
internal fun getDistinctStudents(studentsList: List<Students>, prefStudents: List<Students>): List<Students> {
val studentsIds = studentsList.map { it._id } // [ 1, 2, 3 ]
val prefStudentIds = prefStudents.map { it._id } // [ 1, 2 ]
val commonIds = studentsIds.intersect(prefStudentIds) // [ 1, 2 ]
val allStudents = studentsList + prefStudents // [ Student1, Student2, Student3, Student1, Student2 ]
return allStudents.filter { it._id !in commonIds } // [ Student3 ]
}
If you have a very large amount of students (hundreds), consider using sequences for the various steps, and perhaps filtering before concatenating the last two lists could help too:
val filteredStudents = studentsList.filter { it._id !in commonIds }
val filteredPrefStudents = prefStudents.filter { it._id !in commonIds }
return filteredStudents + filteredPrefStudents
Edit: see this answer instead.
Finally after some searching on Kotlin docs i have the solution. the function i was looking for was filterNot
Here is the complete solution which i tried.
internal fun getDistinctStudents(studentsList: List<Students>, prefStudents: List<Students>): List<Students> {
return prefStudents.filterNot { prefStudent ->
studentsList.any {
prefStudent._id == it._id
}
}
}
Which returned the uncommon elements.
On mobile right now so I can’t test it but this might work for what you need.
Using subtract from stdlib https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/subtract.html
internal fun getDistinctStudents(studentsList: List<Students>, prefStudents:
List<Students>): List<Students> {
return prefStudents.subtract(studentList) + studentList.subtract(prefStudents)
}

Remove data from list while iterating kotlin

I am new to kotlin programming. What I want is that I want to remove a particular data from a list while iterating through it, but when I am doing that my app is crashing.
for ((pos, i) in listTotal!!.withIndex()) {
if (pos != 0 && pos != listTotal!!.size - 1) {
if (paymentsAndTagsModel.tagName == i.header) {
//listTotal!!.removeAt(pos)
listTotal!!.remove(i)
}
}
}
OR
for ((pos,i) in listTotal!!.listIterator().withIndex()){
if (i.header == paymentsAndTagsModel.tagName){
listTotal!!.listIterator(pos).remove()
}
}
The exception which I am getting
java.lang.IllegalStateException
use removeAll
pushList?.removeAll { TimeUnit.MILLISECONDS.toMinutes(
System.currentTimeMillis() - it.date) > THRESHOLD }
val numbers = mutableListOf(1,2,3,4,5,6)
val numberIterator = numbers.iterator()
while (numberIterator.hasNext()) {
val integer = numberIterator.next()
if (integer < 3) {
numberIterator.remove()
}
}
It's forbidden to modify a collection through its interface while iterating over it. The only way to mutate the collection contents is to use Iterator.remove.
However using Iterators can be unwieldy and in vast majority of cases it's better to treat the collections as immutable which Kotlin encourages. You can use a filter to create a new collections like so:
listTotal = listTotal.filterIndexed { ix, element ->
ix != 0 && ix != listTotal.lastIndex && element.header == paymentsAndTagsModel.tagName
}
The answer by miensol seems perfect.
However, I don't understand the context for using the withIndex function or filteredIndex. You can use the filter function just by itself.
You don't need access to the index the list is at, if you're using
lists.
Also, I'd strongly recommend working with a data class if you already aren't. Your code would look something like this
Data Class
data class Event(
var eventCode : String,
var header : String
)
Filtering Logic
fun main(args:Array<String>){
val eventList : MutableList<Event> = mutableListOf(
Event(eventCode = "123",header = "One"),
Event(eventCode = "456",header = "Two"),
Event(eventCode = "789",header = "Three")
)
val filteredList = eventList.filter { !it.header.equals("Two") }
}
The following code works for me:
val iterator = listTotal.iterator()
for(i in iterator){
if(i.haer== paymentsAndTagsModel.tagName){
iterator.remove()
}
}
You can also read this article.
People didn't break iteration in previous posts dont know why. It can be simple but also with extensions and also for Map:
fun <T> MutableCollection<T>.removeFirst(filter: (T) -> Boolean) =
iterator().removeIf(filter)
fun <K, V> MutableMap<K, V>.removeFirst(filter: (K, V) -> Boolean) =
iterator().removeIf { filter(it.key, it.value) }
fun <T> MutableIterator<T>.removeFirst(filter: (T) -> Boolean): Boolean {
for (item in this) if (filter.invoke(item)) {
remove()
return true
}
return false
}
Use a while loop, here is the kotlin extension function:
fun <E> MutableList<E>.removeIfMatch(isMatchConsumer: (existingItem: E) -> Boolean) {
var index = 0
var lastIndex = this.size -1
while(index <= lastIndex && lastIndex >= 0){
when {
isMatchConsumer.invoke(this[index]) -> {
this.removeAt(index)
lastIndex-- // max is decreased by 1
}
else -> index++ // only increment if we do not remove
}
}
}
Typically you can use:
yourMutableCollection.removeIf { someLogic == true }
However, I'm working with an Android app that must support APIs older than 24.
In this case removeIf can't be used.
Here's a solution that is nearly identical to that implemented in Kotlin Collections that doesn't rely on Predicate.test - which is why API 24+ is required in the first place
//This function is in Kotlin Collections but only for Android API 24+
fun <E> MutableCollection<E>.removeIff(filter: (E) -> Boolean): Boolean {
var removed = false
val iterator: MutableIterator<E> = this.iterator()
while (iterator.hasNext()) {
val value = iterator.next()
if (filter.invoke(value)) {
iterator.remove()
removed = true
}
}
return removed
}
Another solution that will suit small collections. For example set of listeners in some controller.
inline fun <T> MutableCollection<T>.forEachSafe(action: (T) -> Unit) {
val listCopy = ArrayList<T>(this)
for (element: T in listCopy) {
if (this.contains(element)) {
action(element)
}
}
}
It makes sure that elements of collection can be removed safely even from outside code.

Categories

Resources