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)
Related
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()
I'm sorry for this question name.
So I have a data class:
data class Day(
val date: Int = 0,
val night: Time? = null,
val morning: Time? = null,
val noon: Time? = null,
val evening: Time? = null,
)
I'm making variable with type of this class and i need to set values to this variable:
val days = ArrayList<Day>()
val listOfDays = this
.groupBy { it.getDayOfMonth() }
.map { WeatherForDay(it.value) }
.take(FIVE_DAYS_FORECAST)
listOfDays.forEach {
val day = Day()
var weather: WeatherList
for (hour in it.weather.indices) {
weather = it.weather[hour]
val time = Time(
weather.main.temp,
weather.main.feels_like,
weather.wind.speed,
weather.wind.gust,
weather.wind.deg,
weather.main.pressure,
)
when (it.weather[hour].getTime()) {
NIGHT_DAYTIME -> day.night = time
MORNING_DAYTIME -> day.morning = time
DAY_DAYTIME -> day.noon = time
EVENING_DAYTIME -> day.evening = time
}
day.date = weather.dt
}
days.add(day)
But i can't because my attributes in data class are values. And i need it to be values. So how can i make this code in a right style with val?
Rearrange your code so you only instantiate the class when you have all the parameters you need.
val listOfDays = this
.groupBy { it.getDayOfMonth() }
.map { WeatherForDay(it.value) }
.take(FIVE_DAYS_FORECAST)
val days = listOfDays.map { listOfDaysDay ->
val date = listOfDaysDay.weather.firstOrNull()?.dt ?: 0
val timeItemByTime = listOfDaysDay.weather
.associateBy { it.getTime() }
.map { with (it) {
Time(
main.temp
main.feels_like,
wind.speed,
wind.gust,
wind.deg,
main.pressure
)
} }
Day(
date,
timeItemByTime[NIGHT_DAYTIME],
timeItemByTime[MORNING_DAYTIME],
timeItemByTime[DAY_DAYTIME],
timeItemByTime[EVENING_DAYTIME]
)
}
I might have some errors above. It's kind of hard to keep your types straight because of your naming scheme. You have a "listOfDays" that is not a list of your Day class but of some other entity. And you have a class named Time that isn't a time but just a bunch of concurrent weather conditions. And you have something else called "time" that is apparently String or Int constants that actually do represent time.
And a suggestion. When you have two totally separate classes with a bunch of the same public properties, you might want to create an intermediate class so you can pass them around without having to manually copy six property values each time. Then if you want to add one more property you don't have to modify a whole bunch of classes.
You may use the day.copy(night = time) method that exists for each data class. If you don't want new allocations the only way that I see is to define local variables for each data class property and set them in the loop, and then, in the end of an iteration, create the instance of the data class on the base of these variables.
I'm trying to implement paging I'm using Room and it took me ages to realize that its all done for me 😆 but what I need to do is be able to filter search and sort my data. I want to keep it as LiveData for now I can swap to flow later.
I had this method to filter search and sort and it worked perfectly,
#SuppressLint("DefaultLocale")
private fun searchAndFilterPokemon(): LiveData<List<PokemonWithTypesAndSpecies>> {
return Transformations.switchMap(search) { search ->
val allPokemon = repository.searchPokemonWithTypesAndSpecies(search)
Transformations.switchMap(filters) { filters ->
val pokemon = when {
filters.isNullOrEmpty() -> allPokemon
else -> {
Transformations.switchMap(allPokemon) { pokemonList ->
val filteredList = pokemonList.filter { pokemon ->
pokemon.matches = 0
val filter = filterTypes(pokemon, filters)
filter
}
maybeSortList(filters, filteredList)
}
}
}
pokemon
}
}
}
It have a few switchmaps here, the first is responding to search updating
var search: MutableLiveData<String> = getSearchState()
the second is responding to filters updating
val filters: MutableLiveData<MutableSet<String>> = getCurrentFiltersState()
and the third is watching the searched list updating, it then calls filterTypes and maybeSortList which are small methods for filtering and sorting
#SuppressLint("DefaultLocale")
private fun filterTypes(
pokemon: PokemonWithTypesAndSpecies,
filters: MutableSet<String>
): Boolean {
var match = false
for (filter in filters) {
for (type in pokemon.types) {
if (type.name.toLowerCase() == filter.toLowerCase()) {
val matches = pokemon.matches.plus(1)
pokemon.apply {
this.matches = matches
}
match = true
}
}
}
return match
}
private fun maybeSortList(
filters: MutableSet<String>,
filteredList: List<PokemonWithTypesAndSpecies>
): MutableLiveData<List<PokemonWithTypesAndSpecies>> {
return if (filters.size > 1)
MutableLiveData(filteredList.sortedByDescending {
Log.d("VM", "SORTING ${it.pokemon.name} ${it.matches}")
it.matches
})
else MutableLiveData(filteredList)
}
as mentioned I want to migrate these to paging 3 and am having difficulty doing it Ive changed my repository and dao to return a PagingSource and I just want to change my view model to return the PagingData as a live data, so far I have this
#SuppressLint("DefaultLocale")
private fun searchAndFilterPokemonPager(): LiveData<PagingData<PokemonWithTypesAndSpecies>> {
val pager = Pager(
config = PagingConfig(
pageSize = 50,
enablePlaceholders = false,
maxSize = 200
)
){
searchAndFilterPokemonWithPaging()
}.liveData.cachedIn(viewModelScope)
Transformations.switchMap(filters){
MutableLiveData<String>()
}
return Transformations.switchMap(search) {search ->
val searchedPokemon =
MutableLiveData<PagingData<PokemonWithTypesAndSpecies>>(pager.value?.filter { it.pokemon.name.contains(search) })
Transformations.switchMap(filters) { filters ->
val pokemon = when {
filters.isNullOrEmpty() -> searchedPokemon
else -> {
Transformations.switchMap(searchedPokemon) { pokemonList ->
val filteredList = pokemonList.filter { pokemon ->
pokemon.matches = 0
val filter = filterTypes(pokemon, filters)
filter
}
maybeSortList(filters, filteredList = filteredList)
}
}
}
pokemon
}
}
}
but the switchmap is giving me an error that
Type inference failed: Cannot infer type parameter Y in
fun <X : Any!, Y : Any!> switchMap
(
source: LiveData<X!>,
switchMapFunction: (input: X!) → LiveData<Y!>!
)
which I think I understand but am not sure how to fix it, also the filter and sort methods won't work anymore and I cant see any good method replacements for it with the PageData, it has a filter but not a sort? any help appreciated
: LiveData<Y!
UPDATE thanks to #Shadow I've rewritten it to implement searching using a mediator live data but im still stuck on filtering
init {
val combinedValues =
MediatorLiveData<Pair<String?, MutableSet<String>?>?>().apply {
addSource(search) {
value = Pair(it, filters.value)
}
addSource(filters) {
value = Pair(search.value, it)
}
}
searchPokemon = Transformations.switchMap(combinedValues) { pair ->
val search = pair?.first
val filters = pair?.second
if (search != null && filters != null) {
searchAndFilterPokemonPager(search)
} else null
}
}
#SuppressLint("DefaultLocale")
private fun searchAndFilterPokemonPager(search: String): LiveData<PagingData<PokemonWithTypesAndSpecies>> {
return Pager(
config = PagingConfig(
pageSize = 50,
enablePlaceholders = false,
maxSize = 200
)
){
searchAllPokemonWithPaging(search)
}.liveData.cachedIn(viewModelScope)
}
#SuppressLint("DefaultLocale")
private fun searchAllPokemonWithPaging(search: String): PagingSource<Int, PokemonWithTypesAndSpecies> {
return repository.searchPokemonWithTypesAndSpeciesWithPaging(search)
}
I do not believe you can sort correctly on the receiving end when you are using a paging source. This is because paging will return a chunk of data from the database in whichever order it is, or in whichever order you specify in its query directly.
Say you have a list of names in the database and you want to display them sorted alphabetically.
Unless you actually specify that sorting in the query itself, the default query will fetch the X first names (X being the paging size you have configured) it finds in whichever order the database is using for the results of that query.
You can sort the results alright, but it will only sort those X first names it returned. That means if you had any names that started with A and they happened to not come in that chunk of names, they will only show when you have scrolled far enough for them to be loaded by the pager, and only then they will show sorted correctly in the present list. You might see names moving around as a result of this whenever a new page is loaded into your list.
That's for sorting, now for search.
What I ended up doing for my search was to just throw away the database's own capability for searching. You can use " LIKE " in queries directly, but unless your search structure is very basic it will be useless. There is also Fts4 available: https://developer.android.com/reference/androidx/room/Fts4
But it is such a PIA to setup and make use of that I ended up seeing absolutely no reward worth the effort for my case.
So I just do the search however I want on the receiving end instead using a Transformations.switchMap to trigger a new data fetch from the database whenever the user input changes coupled with the filtering on the data I receive.
You already have part of this implemented, just take out the contents of the Transformations.switchMap(filters) and simply return the data, then you conduct the search on the results returned in the observer that is attached to the searchAndFilterPokemonPager call.
Filter is the same logic as search too, but I would suggest to make sure to filter first before searching since typically search is input driven and if you don't add a debouncer it will be triggering a new search for every character the user enters or deletes.
In short:
sort in the query directly so the results you receive are already sorted
implement a switchMap attached to the filter value to trigger a new data fetch with the new filter value taken into account
implement a switchMap just like the filter, but for the search input
filter the returned data right before you submit to your list/recyclerview adapter
same as above, but for search
so this is at least partially possible using flows but sorting isnt possible see here https://issuetracker.google.com/issues/175430431, i havent figured out sorting yet but searching and filtering are possible, so for filtering i have it very much the same the query is a LIKE query that triggers by some livedata (the search value) emitting new data heres the search method
#SuppressLint("DefaultLocale")
private fun searchPokemonPager(search: String): LiveData<PagingData<PokemonWithTypesAndSpeciesForList>> {
return Pager(
config = PagingConfig(
pageSize = 50,
enablePlaceholders = false,
maxSize = 200
)
) {
searchAllPokemonWithPaging(search)
}.liveData.cachedIn(viewModelScope)
}
#SuppressLint("DefaultLocale")
private fun searchAllPokemonWithPaging(search: String): PagingSource<Int, PokemonWithTypesAndSpeciesForList> {
return repository.searchPokemonWithTypesAndSpeciesWithPaging(search)
}
and the repo is calling the dao, now to do the filtering #Shadow suggested using the pager this works but is much easier with flow using combine, my filters are live data and flow has some convenient extensions like asFlow so it becomes as easy as
#SuppressLint("DefaultLocale")
private fun searchAndFilterPokemonPager(search: String): Flow<PagingData<PokemonWithTypesAndSpeciesForList>> {
val pager = Pager(
config = PagingConfig(
pageSize = 50,
enablePlaceholders = false,
maxSize = 200
)
) {
searchAllPokemonWithPaging(search)
}.flow.cachedIn(viewModelScope).combine(filters.asFlow()){ pagingData, filters ->
pagingData.filter { filterTypesForFlow(it, filters) }
}
return pager
}
obviously here I've changed the return types so we also have to fix our mediator live data emitting our search and filters as it expects a live data, later ill fix this so it all uses flow
init {
val combinedValues =
MediatorLiveData<Pair<String?, MutableSet<String>?>?>().apply {
addSource(search) {
value = Pair(it, filters.value)
}
addSource(filters) {
value = Pair(search.value, it)
}
}
searchPokemon = Transformations.switchMap(combinedValues) { pair ->
val search = pair?.first
val filters = pair?.second
if (search != null && filters != null) {
searchAndFilterPokemonPager(search).asLiveData()
} else null
}
}
here we just use the asLiveData extesnion
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]) }
I'm making a little application which tracks cryptocurrency values at the Bittrex exchange.
For this I'm using Bittrex' public api (https://bittrex.github.io/api/v3)
Unfortunately the api doesn't provide the data I want with just one call, therefore I need to do two api calls.
What I want to achieve is to have one object containing all of the following values:
symbol (This is a shared value between both api calls, so this needs
to match)
quoteVolume
percentChange
lastTradeRate
The bold variable is part of one api call, the other values are part of the other. 'Symbol' is part of both.
I'm using kotlin coroutines and I was hoping that I don't have to use something like RxJava to get this to work.
CoroutineScope(IO).launch {
val tickers = async {
api.getTickers()
}.await()
val markets = async {
api.getMarkets()
}.await()
val result = mutableListOf<Market>()
for (ticker in tickers.data) {
for (market in markets.data) {
if (ticker.symbol == market.symbol) {
result.add(
Market(
ticker.symbol,
ticker.lastTradeRate,
market.quoteVolume,
market.percentChange
)
)
}
}
}
}
You can make the 2 calls in parallel using coroutines.
Assuming firstApi and secondApi are suspend functions that return the data for each of the 2 blobs of info you need,
val data1Deferred = async { firstApi() }
val data2Deferred = async { secondApi() }
val data1 = data1Deferred.await()
val data2 = data2Deferred.await()
val result = Result(
// build result from data1 and data2
)
You would also need to add error handling.
Edit:
you can group your list by the symbol and generate a map:
val marketMap = markets.associateBy { it.symbol }
Then for each ticker you can get the corresponding market
for (ticker in tickers) {
val market = marketMap[ticker.symbol]
if (market != null) {
// join ticker and market
}
}