Android Kotlin Map Multiple Array into a list of object - android

So in this case I have three array that I want to map into a list of object (The objects has three parameters as well).
I have three arrays allProductCodeList, allProductNameList, and allProductQtyList (Content of this array is from a Retrofit Client response)
allProductCodeList = response.body()?.data?.map { it?.stkProdcode }!!
allProductNameList = response.body()?.data?.map { it?.proName }!!
allProductQtyList = response.body()?.data?.map { it?.stkAllqty }!!
//I printed these arrays to LogCat so it is easier to see
Log.i("Order", allProductCodeList.toString())
Log.i("Order", allProductNameList.toString())
Log.i("Order", allProductQtyList.toString())
This is the content of the array I printed into the LogCat:
This is the Data class which I want to parse these arrays into:
data class ProcodeRecommendationListDataClass(
val procode: String?,
val productName: String?,
val qty: Int?
)
What I want to do is parse these three array into a list that will looks like:
[ProcodeRecommendationListDataClass("0100009","", 2),ProcodeRecommendationListDataClass("0100061","", 1),ProcodeRecommendationListDataClasslass("0100062","", 6)]
I've done it when I only have two arrays to map (I use this solution for it). But now it I have three arrays, I confused.
If there's any detail I miss to point out, Just let me know !

1. This is you three arrays
allProductCodeList = response.body()?.data?.map { it?.stkProdcode }!!
allProductNameList = response.body()?.data?.map { it?.proName }!!
allProductQtyList = response.body()?.data?.map { it?.stkAllqty }!!
2. Make A New List
List<ProcodeRecommendationListDataClass> finalList = List()
3. Run a for loop with any of three array size with indices;
for(pos in allProductCodeList.indices){
finalList.add(ProcodeRecommendationListDataClass(allProductCodeList[pos],
allProductNameList[pos],
allProductQtyList[pos] ))
}
Now finalList is your result.

One forward straight way is to use one more zip - someone once said all problems are solved with one more level of inderection:
allProductCodeList
.zip(allProductNameList)
.zip(allProductQtyList)
.map { (codeAndName, qt) ->
ProcodeRecommendationListDataClass(
codeAndName.first,
codeAndName.second,
qt
)
}
It doesn't look super pretty, but it should be ok.
Another way is to create your own zip that takes 2 lists:
fun <X, Y, Z, R> List<X>.zipWith(l1: List<Y>, l2: List<Z>, transform: (X, Y, Z) -> R): List<R> {
val length = min(min(size, l1.size), l2.size)
val result = mutableListOf<R>()
for (i in 0 until length) {
result.add(transform(get(i), l1[i], l2[i]))
}
return result
}
fun main() {
val k = allProductCodeList.zipWith(allProductNameList, allProductQtyList) { code, name, qt ->
ProcodeRecommendationListDataClass(
code,
name,
qt
)
}
println(k)
}
Basically extends a list of X that takes 2 other lists. It iterates through them applying the transform method (this is so you can map the elements as you go).
This will iterate always the smallest amount of elements - in other words, you won't get more elements than the smallest list. I can't be sure, but I assume the default implementation does something similar.

Why not just create objects in place?
val allProducts = response.body()?.data?.map {
ProcodeRecommendationListDataClass(it?.stkProdcode, it?.proName, it?.stkAllqty)
} ?: emptyList()

You can use mapIndexed instead of map. use index to get third data.
val list = allProductCodeList.zip(allProductNameList)
.mapIndexed { index, pair -> SomeClass(pair.first, pair.second,allProductQtyList[index]) }

Related

Kotlin List<List<String>> when instantiating creates 1 empty element

Solving algorithm tasks and came to one interesting situation that before I did not pay attention to.
Here is example:
val testList1 = mutableListOf<String>()
testList1.add("f")
testList1.add("n")
Toast.makeText(this, testList1.size.toString(), Toast.LENGTH_SHORT).show()
In this code, my toast will return size 2. Which is ok and expected.
but let's take this example:
val testList2 = mutableListOf(mutableListOf<String>())
testList2.add(mutableListOf("sf", "fgs"))
testList2.add(mutableListOf("sw", "fgg"))
Toast.makeText(this, testList2.size.toString(), Toast.LENGTH_SHORT).show()
Here the toast shows size = 3 even though I added 2 elements (2 lists). So when instantiating it adds 1 emptyList as the first element.
Not a big problem to solve this, we can just:
var finalList = testList2.removeIf { it.isEmpty() }
But I am curious why this happens. Also is there any nice way to avoid it. Would like to know little bit more if possible
It is not strange that testList2 contains 3 objects. testList2 is constructed with an initial empty list.
val testList2 = mutableListOf(mutableListOf<String>())
// using
public fun <T> mutableListOf(vararg elements: T): MutableList<T> =
if (elements.size == 0) ArrayList() else ArrayList(ArrayAsCollection(elements, isVarargs = true))
Here, you can define an empty mutable list by these codes.
val testList: MutableList<MutableList<String>> = mutableListOf()
// or
val testList = mutableListOf<MutableList<String>>()
// using
public inline fun <T> mutableListOf(): MutableList<T> = ArrayList()
Whatever you pass to the mutableListOf function is the initial contents of the list it returns. Since you have nested a call of mutableListOf() inside the outer call to mutableListOf(), you are creating your list with an initial value of another MutableList.
If you want your list to start empty, don’t put anything inside the () when you call mutableListOf().
If you construct your list this way, you need to specify the type of the list, since it won’t have an argument to infer the type from.
Either
val testList2 = mutableListOf<MutableList<String>>()
or
val testList2: MutableList<MutableList<String>> = mutableListOf()

Efficeint way to differentiate arraylists of same type

I have 20 arraylists (more to come) containing the same type of data:
var arraylist1 = ArrayList()
var arraylist2 = ArrayList()
var arraylist3 = ArrayList()
And so on.
All the arrays make up a list on different tabs in my app which the user clicks and I can differentiate where the clicks are coming from.
Now I need to clear all arrays except the one where the click came from.
Lets say that I write a function like this
clearAllArraysExceptThis(arraylist1)
Assuming that the click originated from arraylist1. How do I clear all the remaining lists efficiently.
Do I keep a list of the arraylists and get the name loop and compare or is there a better way ?
With kotlin, assuming the following mock lists:
val list1 = arrayListOf(1, 2)
val list2 = arrayListOf(2, 2)
val list3 = arrayListOf(3, 2)
val lists = arrayListOf(list1, list2, list3)
To clear all the lists except the list function parameter, it would look something like this:
fun clearAllArraysExceptThis(listToKeep: List<Int>) {
for (list in lists) {
if (list != listToKeep) list.clear()
}
}
A more functional approach would be:
fun clearAllArraysExceptThis(listToKeep: List<Int>) {
lists.filter { list -> list != listToKeep }
.forEach { list -> list.clear() }
}
If you want to actually remove the lists instead of clearing them, you could use:
fun clearAllArraysExceptThis(listToKeep: List<Int>) {
lists.removeAll { it != listToKeep }
}
You could create an arrayList of arrayLists called "AllArrayListTogether" where you add all yor arrayLists. Then create a function called "clearAllArraysExceptThis" whit one parameter "arrayListSelected".
Then do something like this:
public void clearAllArraysExceptThis(ArrayList arrayListSelected){
for(ArrayList i : AllArrayListTogether){
if(!i.equals(arrayListSelected){
i.clear();
}
}
}

Paging library 3.0 : How to pass total count of items to the list header?

Help me please.
The app is just for receiving list of plants from https://trefle.io and showing it in RecyclerView.
I am using Paging library 3.0 here.
Task: I want to add a header where total amount of plants will be displayed.
The problem: I just cannot find a way to pass the value of total items to header.
Data model:
data class PlantsResponseObject(
#SerializedName("data")
val data: List<PlantModel>?,
#SerializedName("meta")
val meta: Meta?
) {
data class Meta(
#SerializedName("total")
val total: Int? // 415648
)
}
data class PlantModel(
#SerializedName("author")
val author: String?,
#SerializedName("genus_id")
val genusId: Int?,
#SerializedName("id")
val id: Int?)
DataSource class:
class PlantsDataSource(
private val plantsApi: PlantsAPI,
private var filters: String? = null,
private var isVegetable: Boolean? = false
) : RxPagingSource<Int, PlantView>() {
override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, PlantView>> {
val nextPageNumber = params.key ?: 1
return plantsApi.getPlants( //API call for plants
nextPageNumber, //different filters, does not matter
filters,
isVegetable)
.subscribeOn(Schedulers.io())
.map<LoadResult<Int, PlantView>> {
val total = it.meta?.total ?: 0 // Here I have an access to the total count
//of items, but where to pass it?
LoadResult.Page(
data = it.data!! //Here I can pass only plant items data
.map { PlantView.PlantItemView(it) },
prevKey = null,
nextKey = nextPageNumber.plus(1)
)
}
.onErrorReturn{
LoadResult.Error(it)
}
}
override fun invalidate() {
super.invalidate()
}
}
LoadResult.Page accepts nothing but list of plant themselves. And all classes above DataSource(Repo, ViewModel, Activity) has no access to response object.
Question: How to pass total count of items to the list header?
I will appreciate any help.
You can change the PagingData type to Pair<PlantView,Int> (or any other structure) to add whatever information you need.
Then you will be able to send total with pages doing something similar to:
LoadResult.Page(
data = it.data.map { Pair(PlantView.PlantItemView(it), total) },
prevKey = null,
nextKey = nextPageNumber.plus(1)
)
And in your ModelView do whatever, for example map it again to PlantItemView, but using the second field to update your header.
It's true that it's not very elegant because you are sending it in all items, but it's better than other suggested solutions.
Faced the same dilemma when trying to use Paging for the first time and it does not provide a way to obtain count despite it doing a count for the purpose of the paging ( i.e. the Paging library first checks with a COUNT(*) to see if there are more or less items than the stipulated PagingConfig value(s) before conducting the rest of the query, it could perfectly return the total number of results it found ).
The only way at the moment to achieve this is to run two queries in parallel: one for your items ( as you already have ) and another just to count how many results it finds using the same query params as the previous one, but for COUNT(*) only.
There is no need to return the later as a PagingDataSource<LivedData<Integer>> since it would add a lot of boilerplate unnecessarily. Simply return it as a normal LivedData<Integer> so that it will always be updating itself whenever the list results change, otherwise it can run into the issue of the list size changing and that value not updating after the first time it loads if you return a plain Integer.
After you have both of them set then add them to your RecyclerView adapter using a ConcatAdapter with the order of the previously mentioned adapters in the same order you'd want them to be displayed in the list.
ex: If you want the count to show at the beginning/top of the list then set up the ConcatAdapter with the count adapter first and the list items adapter after.
One way is to use MutableLiveData and then observe it. For example
val countPlants = MutableLiveData<Int>(0)
override fun loadSingle(..... {
countPlants.postValue(it.meta?.total ?: 0)
}
Then somewhere where your recyclerview is.
pagingDataSource.countPlants.observe(viewLifecycleOwner) { count ->
//update your view with the count value
}
The withHeader functions in Paging just return a ConcatAdapter given a LoadStateHeader, which has some code to listen and update based on adapter's LoadState.
You should be able to do something very similar by implementing your own ItemCountAdapter, except instead of listening to LoadState changes, it listens to adapter.itemCount. You'll need to build a flow / listener to decide when to send updates, but you can simply map loadState changes to itemCount.
See here for LoadStateAdapter code, which you can basically copy, and change loadState to itemCount: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:paging/runtime/src/main/java/androidx/paging/LoadStateAdapter.kt?q=loadstateadapter
e.g.,
abstract class ItemCountAdapter<VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {
var itemCount: Int = 0
set(itemCount { ... }
open fun displayItemCountAsItem(itemCount: Int): Boolean {
return true
}
...
Then to actually create the ConcatAdapter, you want something similar to: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:paging/runtime/src/main/java/androidx/paging/PagingDataAdapter.kt;l=236?q=withLoadStateHeader&sq=
fun PagingDataAdapter.withItemCountHeader(itemCountAdapter): ConcatAdapter {
addLoadStateListener {
itemCountAdapter.itemCount = itemCount
}
return ConcatAdapter(itemCountAdapter, this)
}
Another solution, although also not very elegant, would be to add the total amount to your data model PlantView.
PlantView(…val totalAmount: Int…)
Then in your viewmodel you could add a header with the information of one item. Here is a little modified code taken from the official paging documenation
pager.flow.map { pagingData: PagingData<PlantView> ->
// Map outer stream, so you can perform transformations on
// each paging generation.
pagingData
.map { plantView ->
// Convert items in stream to UiModel.PlantView.
UiModel.PlantView(plantView)
}
.insertSeparators<UiModel.PlantView, UiModel> { before, after ->
when {
//total amount is used from the next PlantView
before == null -> UiModel.SeparatorModel("HEADER", after?.totalAmount)
// Return null to avoid adding a separator between two items.
else -> null
}
}
}
A drawback is the fact that the total amount is in every PlantView and it's always the same and therefore redundant.
For now, I found this comment usefull: https://issuetracker.google.com/issues/175338415#comment5
There people discuss the ways to provide metadata state to Pager
A simple way I found to fix it is by using a lambda in the PagingSource constructor. Try the following:
class PlantsDataSource(
// ...
private val getTotalItems: (Int) -> Unit
) : RxPagingSource<Int, PlantView>() {
override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, PlantView>> {
...
.map<LoadResult<Int, PlantView>> {
val total = it.meta?.total ?: 0
getTotalItems(total)
...
}
...
}
}

Java 8 "Optional" bad practice?

I've a code:
val d = Single
.zip<List<X>, Optional<Y>, DataContent>(
xSingle,
YSingle,
BiFunction { x, y ->
val b = if (y.isPresent()) {
y.get()
} else {
null
}
return#BiFunction DataContent(x, b)
})
.subscribe({ data ->
...
}, { t ->
...
})
I've heard, that using Optional to check null value as shown in an example, is bad practice. Is that true? Why? Can someone show an alternative using RxJava2?
In general, Optional has a restricted set of use cases and is in danger of being overrused. You can refer to this answer by Java author Brian Goetz to understand these (emphasis added):
But we did have a clear intention when adding this [java.util.Optional] feature, and it was not to be a general purpose Maybe or Some type, as much as many people would have liked us to do so. Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result", and using null for such was overwhelmingly likely to cause errors.
For example, you probably should never use it for something that returns an array of results, or a list of results; instead return an empty array or list. You should almost never use it as a field of something or a method parameter.
In the original example posted, Optional<Y> is used as a method parameter so this is against Java best practices. Besides, Maybe is idiomatic in RxJava.
Assuming you have something like the following:
class X
class Y
data class DataContent constructor(val listOfX: List<X>, val y: Y)
You could write a function like this that would seem to fit your use case:
fun zipToDataContent(maybeListOfX: Maybe<List<X>>, maybeY: Maybe<Y>): Maybe<DataContent> =
Maybe.zip<List<X>, Y, DataContent>(
maybeListOfX,
maybeY,
BiFunction { listOfX, y -> DataContent(listOfX, y) })
Tests:
#Test
fun testZipToDataContentWithRealY() {
val x = X()
val y = Y()
val maybeListOfX = Maybe.just(listOf(x))
val maybeY = Maybe.just(y)
zipToDataContent(maybeListOfX, maybeY).test()
.assertOf {
Maybe.just(DataContent(listOf(x), y))
}
}
#Test
fun testZipToDataContentWithEmptyY() {
val x = X()
val maybeListOfX = Maybe.just(listOf(x))
val maybeY = Maybe.empty<Y>()
zipToDataContent(maybeListOfX, maybeY).test()
.assertOf {
Maybe.empty<DataContent>()
}
}

Sorting Objects Alphanumerically

I am running into an issue when trying to sort a List of "Routes" for my app, no matter what I try, I cannot obtain the sorting that I am looking for.
I want it sorted 1,2,3,4,5, etc but when I sort, I get 1,11,12,2,20 and so on.
My Route model is
public open class Route(docValue:Map<String,Any>) {
val route_id = (docValue["route_id"] as Number).toInt()
val short_name = docValue["route_short_name"] as String
val color = readColorMoreSafely(docValue, "route_color", Color.BLUE)
val long_name = docValue["route_long_name"] as String
}
The code used to sort is
if(cityId != null && view != null) {
val routesList = view.findViewById(R.id.routesList) as ListView
val cache = TransitCache.getInstance(applicationContext, cityId, true)
val routes = cache.getRoutes()
.observeOn(AndroidSchedulers.mainThread())
.doOnNext {
val noRoutesMessage = view.findViewById(R.id.list_routes_no_routes_visible) as TextView
noRoutesMessage.visibility = if(it.size == 0) View.VISIBLE else View.GONE
}
routes.toSortedList()
listAdapter = RxListAdapter(applicationContext, R.layout.activity_list_routes_row, routes)
routesList.adapter = listAdapter
But still nothing, I just want to sort the routes by "route_id", i've tried a few different things, the last one of which was
routes.toSortedList()
which still ended up not doing what I wanted, at this point I'm stuck.
val routes = cache.getRoutes()
.observeOn(AndroidSchedulers.mainThread())
This code tells me you're dealing with RxJava, which requires an entirely different solution so in the future it is important to include that type of information.
If cache.getRoutes() returns an Observable<List<Route>> then that route can be sorted with the code
.map {
it.sortedBy(Route::route_id)
}
This will produce a new inner list sorted by the numerical value of route_id.
If cache.getRoutes() returns Observable<Route> then you need to include the additional call to .toList() to turn it into an Observable<List<Route>>.
If routes is a MutableList and you want to sort it in-place then you can use sortBy:
routes.sortBy(Route::route_id)
Otherwise you can use sortedBy to create a new list with the elements sorted:
val sortedRoutes = routes.sortedBy(Route::route_id)

Categories

Resources