I have a method which takes a list of object (Widget) -- which contains some properties (header) and nested list(component). I want to flatten the list into a single list and have the below code for same:
#SuppressLint("CheckResult")
fun flatten(fatList: Single<List<Widget>>) {
val flatList: MutableList<IUiData> = mutableListOf()
fatList.map {
Observable.fromIterable(it).map { widget ->
if (widget.header.isNotEmpty()) {
flatList.add(ProductHeaderUi(widget.header))
}
widget.componentList.map { component ->
when (component.type) {
TILE_TEXT -> {
flatList.add(HeaderUi(component))
}
TILE_IMAGE -> {
flatList.add(ImageTileUi(component))
}
TILE_FOOTER -> {
flatList.add(FooterUi(component))
}
UNKNOWN -> {
//Do Nothing
}
}
}
}
}
}
I intend to return a Single of List: Single<MutableList<IUiData>> from this method, this purpose can be served right now, but I am looking for a cleaner way
You're using both Rx's Observable map and Kotlin's Iterable map in an unintended way. They are for converting one type to another, not for iterating something.
You've also nested an unnecessary Observable iterable inside the outer-most map function.
You only need to map the output of the Single. Inside the map function, you iterate (not map) the original List to pull out the data you need for the MutableList.
I'm an Rx novice and didn't check this, so sorry about any syntax errors.
fun flatten(fatList: Single<List<Widget>>): Single<MutableList<IUData>> = fatList.map { widgetList ->
val flatList: MutableList<IUiData> = mutableListOf()
for (widget in widgetList) {
if (widget.header.isNotEmpty()) {
flatList.add(ProductHeaderUi(widget.header))
}
for (component in widget.componentList) {
when (component.type) {
TILE_TEXT -> flatList.add(HeaderUi(component))
TILE_IMAGE -> flatList.add(ImageTileUi(component))
TILE_FOOTER -> flatList.add(FooterUi(component))
// Else do nothing
}
}
}
flatList
}
But in keeping with typical Rx chaining syntax, I would make it an extension function, so I'd have to first line like this. Then you can put it right in the middle of an Rx call chain:
fun Single<List<Widget>>.flatten(): Single<MutableList<IUData>> = map { widgetList ->
You can also do this in a more concise, functional, but less efficient way by using Kotlin's flatMap:
fun Single<List<Widget>>.flatten(): Single<MutableList<IUData>> = map {
it.flatMap { widget ->
listOfNotNull(widget.header.takeIf(Header::isNotEmpty)?.let(::ProductHeaderUi))
+
widget.componentList.mapNotNull { component ->
when (component.type) {
TILE_TEXT -> HeaderUi(component)
TILE_IMAGE -> ImageTileUi(component)
TILE_FOOTER -> FooterUi(component)
else -> null
}
}.toMutableList()
}
...where Header is whatever type widget.header uses.
Related
I need to search and sort data simultaneously. I did it for search but it wont trigger for sort. I'm also using pagination.
User can type in searchView and flow will trigger, but problem is when i change sortState (ascending or descending) it wont trigger flow for searching articles on api endpoint.
ViewModel:
private val currentQuery = MutableStateFlow(DEFAULT_QUERY)
private val sortState = MutableStateFlow<SortOrderState>(SortOrderState.Ascending)
val flow = currentQuery
.debounce(2300)
.filter {
it.trim().isNotEmpty()
}
.distinctUntilChanged()
.flatMapLatest { query ->
articleRepository.getSearchResult(query.lowercase(Locale.ROOT),sortState.value)
}
Fragment:
lifecycleScope.launch {
viewModel.flow.collectLatest { articles ->
binding.recyclerViewTop.layoutManager = LinearLayoutManager(context)
binding.recyclerViewTop.adapter = adapter.withLoadStateHeaderAndFooter(
header = ArticleLoadStateAdapter { adapter.retry() },
footer = ArticleLoadStateAdapter { adapter.retry() }
)
adapter.submitData(articles)
}
}
In fragment I have function: viewModel.searchNews(newText)
And in Main activity: viewModel.setSortState(SortOrderState.Ascending) (one menu item clicked) to see if MutableStateFlow.value is changed. I can see that in ViewModel i can change these values but if I do:
val flow=currentQuery.combine(sortState){
query,state ->
}
I never changes if I click on sort menu item, only if I type something to search.
Edit: sortState is not updating in flow variable, I checked setSortState and I can clearly see that state is changed but in flow I only send ascending all the time.
Main activity:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_sortAsc -> {
viewModel.setSortState(SortOrderState.Ascending)
}
R.id.menu_sortDesc -> {
viewModel.setSortState(SortOrderState.Descening)
}
}
return super.onOptionsItemSelected(item)
}
ViewModel:
fun setSortState(sortOrderState: SortOrderState) {
sortState.value = sortOrderState
}
SortOrderState:
sealed interface SortOrderState{
object Ascending : SortOrderState
object Descening : SortOrderState
}
Edit 2: Collecting in HomeFragment it always gives me Ascending value even if i click on menu item for descending sort
lifecycleScope.launch {
viewModel.sortState.collectLatest {
Log.d(TAG, "onCreateViewSort: $it")
}
In ViewModel I can see sortState is changed:
fun setSortState(sortOrderState: SortOrderState) {
sortState.value = sortOrderState
Log.d(TAG, "setSortState: ${sortState.value}")
}
You aren't using your sort state as a Flow. You're only passively using its value, so your output flow won't automatically update when the value changes.
Instead, you need to combine your flows.
Here, I also moved your lowercase transformation before the distinctUntilChanged because I think that makes more logical sense. Also, it makes sense to include the trim in the transformation and not just in the filter.
val flow = currentQuery
.debounce(2300)
.map { it.trim().lowercase(Locale.ROOT) }
.filter { it.isNotEmpty() }
.distinctUntilChanged()
.combine(sortState) { query, sort -> query to sort }
.flatMapLatest { (query, sort) ->
articleRepository.getSearchResult(query, sort)
}
You might also consider tagging this with shareIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 1) so the search doesn't have to restart on a screen rotation.
I currently face the problem of correctly closing resources that never leave their containing Either.
The relevant code looks something like this:
object SomeError
class MyRes : AutoCloseable { [...] }
fun createRes(): Either<SomeError, MyRes> { [...] }
fun extractData(res: MyRes): String { [...] }
fun theProblem(): Either<SomeError, String> {
return createRes()
.map { extractData(it) }
}
What is the most idiomatic way of closing the created MyRes? Closing it before that map prevents extractData from accessing it, and after the map I can't access it anymore via Either's operations. Closing it in extractData severely limits composability.
Currently I have an external List<AutoCloseable> that I iterate over after all the computations, but that can't be the intended way.
I am open to using Arrow Fx (e.g. Resource) if that helps, but I haven't found anything on how to combine Either and Resource in an elegant way.
It's possible to combine the either and Resource safely.
object SomeError
class MyRes : AutoCloseable { [...] }
fun createRes(): Resource<Either<SomeError, MyRes>> { [...] }
fun extractData(res: MyRes): String { [...] }
suspend fun solution(): Either<SomeError, String> = either {
createRes().use { either: Either<SomeError, MyRes> ->
val res = either.bind()
val string = extractData(res)
// call other Either code + `bind()` safely here
[...]
} // <-- MyRes will automatically close here
}
If in this code you encounter Either.Left and you call bind() on it the Resource will first close, because we jump outside of use, and then either will return the encountered Either.Left.
One possible solution I found was wrapping the block passed to map:
fun <B : AutoCloseable, C> andClose(f: (B) -> C): (B) -> C =
{ b: B -> b.use { f(b) } }
fun theProblemSlightlySolved(): Either<SomeError, String> {
return createRes()
.map(andClose { extractData(it) })
}
I'm building an application which consists of two API calls. I'm still learning RxJava and I'm not sure how to combine properly the two API calls
The first API call is used to retrieve items in form of a list
The second API call is used to retrieve item image using the item name that I got from the first call.
I need to show all the items with their images. Those are my API calls using retrofit
#GET("items/list")
fun getItems(): Observable<ItemResult>
#GET("item/{name}/images")
fun getItemDetails(#Path("name") name: String): Observable<ItemDetails>
This is the code that wrote with RxJava:
fun getItemsData(): Observable<ArrayList<ItemDetails>> {
val data = ArrayList<ItemDetails>()
getItems().flatMap { itemResponse -> Observable.just(itemResponse.message) } //this will give me a list with item names
.flatMapIterable { data -> data }//iterating over the list and for every item...
.map { itemName ->//calling to get the item image
getItemDetails(itemName).map { imageData ->
val itemImage = imageData.message
data.add(ItemData(itemName, itemImage))//from this point on I'm lost, I'm not sure if it's the right thing to add here the data
}.subscribeOn(Schedulers.io())
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
return Observable.create(data)// not sure how to create an Observable from the data
}
You don't really need this...
val data = ArrayList<ItemDetails>()
flatMap is not the right operator here:
flatMap { itemResponse -> Observable.just(itemResponse.message) }
you can simplify this by using map instead (the function itemResponse -> itemResponse.message operates on the inner value only).
map { itemResponse -> itemResponse.message }
Next:
.flatMapIterable { data -> data } // here we have Observable<Message>
.flatMap { itemName ->
getItemDetails(itemName).map { imageData ->
ItemData(itemName, imageData)
}
} // Observable<ItemData>
.toList() // Single<List<ItemData>> -> you can use toObservable to get an Observable<List<ItemData>>
You can use toList() instead of manually creating and populating the ArrayList, it simplifies things.
You need to map the flat mapped stream to return the desired type -
getItems()
.flatMap(item -> getItemDetails(item)
.map(itemDetail -> ItemData(item, itemDetail.image))
.subscribe(itemData -> // your desired type containing the original item and image);
You're not subscribing to the inner-stream inside the map. Try this:
val data = ArrayList<ItemDetails>()
getItems().flatMap { itemResponse -> Observable.just(itemResponse.message) } //this will give me a list with item names
.flatMapIterable { data -> data }//iterating over the list and for every item...
.flatmap(itemName -> getItemDetails(itemName).subscribeOn(Schedulers.io())
.map(itemDetail -> {//add to the list)})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
return Observable.fromIterable(data);
If you really wish to return an Observable<ArrayList<ItemDetails>>, may be you could use reduce?
Something like that:
fun getItemsData(): Observable<ArrayList<ItemDetails>> {
return
getItems().flatMap { itemResponse -> Observable.just(itemResponse.message) }
.flatMapIterable { data -> data }
.flatMap { itemName -> getItemDetails(itemName) }.subscribeOn(Schedulers.io())
.map { imageData -> ItemData(itemName, imageData.message) } }
.reduce(ArrayList<ItemDetails>(), (list, item) -> list.add(item))
.toObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
I am not sure about the right syntax of reduce in Kotlin...
I'm having a lot of difficulty figuring out a good way to coordinate using RxJava along with the arrow-kt Either and Option types. I have two methods that both return Single<Either<ApiError, Option>
class Foo(val qux: Option<Qux>)
class Bar
class Qux
class ApiError
fun loadFoo(): Single<Either<ApiError, Option<Foo>>> {
...
}
fun loadBar(qux: Qux): Single<Either<ApiError, Option<Bar>>> {
...
}
The goal is to return the result of loadBar(Qux) in an RxJava Single as the type Either<ApiError, Option<Bar>>.
The complication comes from the fact that the qux parameter required by loadBar() is retrieved from the data emitted by the Single returned by loadFoo() (Qux is a property of Foo with the type Option<Qux>).
Desired outcome:
Any ApiErrors that occur get passed to the Single's subscriber in Either.Left
If both loadFoo() and loadBar() return Some, that value should be returned in the composed Single as Either.Right
If either loadFoo() or loadBar() return None, the expected result is Either.Right(None)
I tried a couple things. This first example works, but the resulting code is hard to read because of a bunch of nested folds, as well as intermixing of RxJava and Either/Option operators.
fun loadBarFromMaybeFoo(maybeFoo: Option<Foo>): Single<Either<ApiError, Option<Bar>>> {
return maybeFoo.flatMap { foo -> foo.qux }
.map { qux -> loadBar(qux) }
.getOrElse { Single.just(Either.Right(Option.empty())) }
}
fun doStuffToGetBar(): Single<Either<ApiError, Option<Bar>>> {
return loadFoo()
.flatMap { maybeFooOrError ->
maybeFooOrError.fold(
{ error -> Single.just(Either.Left(error)) },
{ maybeFoo -> loadBarFromMaybeFoo(maybeFoo) }
)
}
}
The second thing I tried was to use arrow's rxjava observable comprehensions. But couldn't quite figure out how to get this to return Single<Either<ApiError, Option> in the end.
fun doStuffToGetBar(): Single<Either<ApiError, Option<Bar>>> {
return SingleK.monadDefer().bindingCatch {
val maybeFooOrError: Either<ApiError, Option<Foo>> = loadFoo().k().bind()
val maybeQuxOrError: Either<ApiError, Option<Qux>> = maybeFooOrError.map { maybeFoo ->
maybeFoo.flatMap { it.qux }
}
// return type is Either<ApiError, Option<Either<ApiError, Option<Bar>>>>
// desired return type is Either<ApiError, Option<Bar>>
maybeQuxOrError.map { maybeQux ->
maybeQux.map { qux ->
loadBar(qux).k().bind() // this part doesn't seem good
}
}
}.value()
}
Any help/advice on how to solve this or restructure the data types to make it easier would be much appreciated! Still pretty new to many functional programming concepts.
If I were you, I would consider simplifying your return types and not use Either in the context of a Single, as Single already can emit an error. So in the end, instead of flat mapping over an Either<ApiError, Option<Bar>>, you could only work with an Option<Bar>, and handle the errors in the RxJava chain. Something like:
class Foo(val qux: Option<Qux>)
class Bar
class Qux
class ApiError
fun loadFoo(): Single<Option<Foo>> {
// in case of an error, this will return Single.error(ApiError(...)) if ApiError extends Throwable
// otherwise make it extend it or just wrap it into something which is a Throwable
}
fun loadBar(qux: Qux): Single<Option<Bar>> {
// same as above
}
fun loadBarFromFooOption(maybeFoo: Option<Foo>): Single<Option<Bar>> {
return maybeFoo.flatMap { foo -> foo.qux }
.map { qux -> loadBar(qux) }
.getOrElse { Single.just(Option.empty()) }
}
fun doStuffToGetBar(): Single<Option<Bar>> {
return loadFoo().flatMap { fooOption -> loadBarFromFooOption(fooOption) }
}
// somewhere else
doStuffToGetBar().subscribe({ barOption -> /* ... */ }, { error -> /* ... */ })
I'm trying to figure out a way to eliminate mutable state and therefore possible race condition. But I can't seem to figure out how to somehow "intertwine" two Observables, while also using "scan".
Hopefully by showing more code I can give you the idea:
private val stateRelay: BehaviorRelay<State> = BehaviorRelay.createDefault(initialState ?: DEFAULT_STATE) // maybe this should be `Observable.startWith()` somehow?
fun bindIntents(intents: Observable<Actions>, stateRenderer: StateRenderer) {
compositeDisposable += intents.concatMap { action ->
when (action) {
is Actions.Increment -> {
Observable.create<Change> { emitter ->
// emit things
}
}
is Actions.Decrement -> {
Observable.create<Change> { emitter ->
// emit things
}
}
}
}.map { change ->
reducer(stateRelay.value, change) // TODO: figure out how to use scan() here, instead of stateRelay.value! :(
}.subscribeBy { newState ->
stateRelay.accept(newState) // there is a chance that the relay shouldn't be here if scan is used
}
compositeDisposable +=
stateRelay // TODO: figure out how to use scan() instead of a relay!
.distinctUntilChanged()
.subscribeBy { state ->
stateRenderer(state)
}
}
fun unbindIntents() {
compositeDisposable.clear()
}
So I'm receiving a Observable<Actions> in this method, which is technically a PublishRelay on the other side (this should be fine).
However, somehow I'm supposed to replace the BehaviorRelay with Observable.scan() (possibly with startWith) to eliminate the mutable state, but I can't seem to wrap my head around what I'm supposed to do for that to happen.
As for the types involved, in case they are needed:
private typealias Reducer = (state: State, change: Change) -> State
private typealias StateRenderer = (state: State) -> Unit
#Parcelize
data class State(val count: Int): Parcelable
How could I wrap intents.concatMap.map, as part of an Observable.scan() (with possibly startWith() and replay(1)), to eliminate my usage of the BehaviorSubject?
I'll elaborate on my comment above.
This is a simple rewrite of your code to do what you're asking for.
fun bindIntents(intents: Observable<Actions>, stateRenderer: StateRenderer) {
val stateObservable = intents.concatMap { action ->
when (action) {
is Actions.Increment -> {
Observable.create<Change> { emitter ->
// emit things
}
}
is Actions.Decrement -> {
Observable.create<Change> { emitter ->
// emit things
}
}
}
}.scan(initialState, { currentState, change -> reducer(currentState, change)})
compositeDisposable +=
stateObservable
.distinctUntilChanged()
.subscribeBy { state ->
stateRenderer(state)
}
}
note that this can be simplified further by inlining the observable I assign to stateObservable in the expression below and using a method reference as the second argument to scan like this
fun bindIntents(intents: Observable<Actions>, stateRenderer: StateRenderer) {
compositeDisposable +=
intents.concatMap { action ->
when (action) {
is Actions.Increment -> {
Observable.create<Change> { emitter ->
// emit things
}
}
is Actions.Decrement -> {
Observable.create<Change> { emitter ->
// emit things
}
}
}
}.scan(initialState, this::reducer)
.distinctUntilChanged()
.subscribeBy { state ->
stateRenderer(state)
}
}