Problems understanding RX philosophy (RxJava, RxJS, Rx...) on android - android

Here a case with using reactive programming (with RxJava for instance)
There is a User object with some properties (name, surname) - the observable
An activity contains 2 fragments both showing the current user name - the subscribers
The user changes (name changes)
Is the assumption right that the displayed name should change automatically if the observed source of data changes (if both fragments are subscribed to the same user object)?
From what I have seen now all examples about rxjava and android focus on async calls and handling streams of returned data triggered/called by subscription. What should / will happen if the source being observed changes? Are subscribers supposed to be triggered or not?
Taken from here:
https://en.wikipedia.org/wiki/Reactive_programming
For example, in an imperative programming setting, a:=b+c would mean that a is being assigned the result of b+c in the instant the expression is evaluated, and later, the values of b and c can be changed with no effect on the value of a. However, in reactive programming, the value of a would be automatically updated whenever the values of b and c change, without the program executing the sentence a:=b+c again.
Are there any examples of how to setup the behavior with rxJava as described in the wikipedia article?

If you create observable that shares updates on the User object(hint: subject/operator) and both fragments get the same observable of the user and subscribe to it then they will get the new version of the User.
As to the Wikipedia example, given you express b and c as observables this can be easily done using combineLatest operator:
Subject<Integer> sb;
Subject<Integer> sc;
Observable.combineLatest(sb, sc, (b, c) -> b + c)
.subscribe(outcome ->
System.out.println("Always up to date value here: " + outcome)
);

Related

Collect flow but only any new values, not the currently existing value

Currently struggling with this one, and so far no combination of SharedFlow and StateFlow have worked.
I have a flow that might have already started with a value, or not.
Using that flow I want to collect any new values that are emitted after I start collecting.
At this moment all my attempts have always failed, no matter what I try it always gets the current value as soon as I start collecting.
An example of what I am trying to achieve:
Given a Flow (could be any type, Int is just for simplification)
with the following timeline: value 4 is emitted | value 2 is emitted | value 10 is emitted
I want to be able to do the following:
If I start collecting after value 4 has already been emitted, I want to only receive anything after that, in this case it would collect 2 and 10 once emitted
If I start collecting after value 2 then it would only receive the 10
If I start collecting before 4 then it would receive 4, 2 and 10
Tried SharedFlow and Stateflow, tried with replay = 0 and WhileSubscribed, no combination I could find would do what I am looking for.
The only workaround so far that I found was to locally register the time I start my .collect{ } and compare with the start time of the item I receive in the collect. In this case I have the object I am using has a specific origin time, but this workaround will not work for everything like the example above with Integers.
EDIT: Adding implementation example as requested for SharedFlow
This is tied to a Room database call that returns a Flow<MyObject>
MyFragment.kt
lifecycleScope.launch(Dispatchers.IO) {
viewModel.getMyObjectFlow.shareIn(
viewModel.viewModelScope, // also tried with fragment lifecyclescope
SharingStarted.WhileSubscribed(), // also tried with the other 2 options
replay = 0,
).collect{
...
}
}
You have a misconception of how flows work. They always emit only after you start collecting. They emit on-demand. Let's get this example:
val flow1 = flow {
println("Emitting 1")
emit(1)
delay(10.seconds)
println("Emitting 2")
emit(2)
}
delay(5.seconds)
println("Start collecting")
flow1.collect {
println("Collected: $it")
}
The output is:
Start collecting
Emitting 1
Collected: 1
not:
Emitting 1
Start collecting
Collected: 1
This is because flow starts emitting only after you start collecting it. Otherwise, it would have nowhere to emit.
Of course, there are flows which emit from some kind of a cache, queue or a buffer. For example shared flows do this. In that case it looks like you collect after emitting. But this is not really the case. Technically speaking, it works like this:
val buffer = listOf(1 , 2, 3)
val flow1 = flow {
buffer.forEach {
println("Emitting $it")
emit(it)
}
}
It still emits after you start collecting, but it just emits from the cache. Of course, the item was added to the cache before you started collecting, but this is entirely abstracted from you. You can't know why a flow emitted an item. From the collector perspective it always emitted just now, not in the past. Similarly, you can't know if a webserver read the data from the DB or a cache - this is abstracted from you.
Summing up: it is not possible to collect only new items from just any flow in an universal way. Flows in general don't understand the concept of "new items". They just emit, but you don't know why they do this. Maybe they somehow generate items on-the-fly, maybe they passively observe external events or maybe they re-transmit some items that they collected from another flow. You don't know that.
While developing your solution, you need to understand what was the source of items and develop your code accordingly. For example, if the source is a regular cold flow, then it never starts doing anything before you start collecting. If the source is a state flow, you can just drop the first item. If it is a shared flow or a flow with some replay buffer, then the situation is more complicated.
One possible approach would be to start collecting earlier than we need, initially ignore all collected items and at some point in time start processing them. But this is still far from perfect and it may not work as we expect.
It doesn't make sense to use shareIn at the use site like that. You're creating a shared Flow that cannot be shared because you don't store the reference for other classes to access and use.
Anyway, the problem is that you are creating the SharedFlow at the use site, so your shared flow only begins collecting from upstream when the fragment calls this code. If the upstream flow is cold, then you will be getting the first value emitted by the cold flow.
The SharedFlow should be created in the ViewModel and put in a property so each Fragment can collect from the same instance. You'll want to use SharingStarted.Eagerly to prevent the cold upstream flow from restarting from the beginning when there are new subscribers after a break.

Android architecture LiveData and Repositories

I am converting my application to room database and try to follow the google architecture best practices based on "Room with a View".
I am having trouble to understand the repository in terms of clean architecture.
The Words database example contains only one table and one view using it, making it a simple HelloWorld example. But lets start with that.
There is a view which displays a list of words. Thus all words need to be read from the database and displayed.
So we have a MainActivity and a Database to connect.
Entity Word
WordDao to access DB
WordViewModel: To separate the activity lifecycle from the data lifecycle a ViewModel is used.
WordRepository: Since the data maybe kept in a database or the cloud or whatever the repository is introduced to handle decision, where data comes from.
Activity with the View
It would be nice if the view is updated when the data changes, so LiveData is used.
This in turn means, the repository is providing the LiveData for the full table:
// LiveData gives us updated words when they change.
val allWords: LiveData<List<Word>>
This is all fine for a single view.
Now to my questions on expanding this concept.
Let us assume, the word table has two columns "word" and "last_updated" as time string.
For easier comparison the time string needs to be converted to milliseconds, so I have a function.
Question: Where to put the fun queryMaxServerDateMS() to get the max(last_updated)?
/**
* #return Highest server date in table in milliseconds or 1 on empty/error.
*/
fun queryMaxServerDateMS(): Long {
val maxDateTime = wordDao.queryMaxServerDate()
var timeMS: Long = 0
if (maxDateTime != null) {
timeMS = parseDateToMillisOrZero_UTC(maxDateTime)
}
return if (timeMS <= 0) 1 else timeMS
}
For me it would be natural to put this into the WordRepository.
Second requirement: Background job to update the word list in the database.
Suppose I now want a Background Job scheduled on a regular basis which checks the server, if new entries were made and downloads them to the database. The app may not be open.
This question just relays to the question of the above queryMaxServerDateMS.
The job will basically check first, if a new entry was made by asking the server if an entry exists which is newer then the max known entry.
So I would need to get a new class WordRepository, do my query, get max last_update and ask the server.
BUT: I do not need the LiveData in the background job and when val repositoy = WordRepository the full table is read, which is needless and time-, memory and batteryconsuming.
I also can think of a number of different fragments that would require some data of the word table, but never the full data, think of a product detail screen which lists one product.
So I can move it out to another Repository or DbHelper however you want to call it.
But in the end I wonder, if I use LiveData, which requires the View, ViewModel and Repository to be closely coupled together:
Question: Do I need a repository for every activity/fragment instead of having a repository for every table which would be much more logical?
Yes, with your current architecture you should put it in the Repository.
No, you don't need a repository for every activity/fragment. Preferably, 1 repository should be created for 1 entity. You can have a UseCase for every ViewModel.
In Clean architecture there's a concept of UseCase / Interactor, that can contain business logic, and in Android it can act as an additional layer between ViewModel and Repository, you can create some UseCase class for your function queryMaxServerDateMS(), put it there and call it from any ViewModel you need.
Also you can get your LiveData value synchronously, by calling getValue().
You do not need repository for each activity or fragment. To answer your question about getting max server time - when you load words from db you pretty much have access to entire table. That means you can either do that computation yourself to decide which is the latest word that's added or you can delegate that work to room by adding another query in dao and access it in your repo. I'd prefer latter just for the simplicity of it.
To answer your question about using repo across different activities or fragment - room caches your computations so that they are available for use across different users of your repo (and eventually dao). This means if you have already computed the max server time in one activity and used it there, other lifecycle owners can use that computed result as far as the table has not been altered (there might be other conditions as well)
To summarize you're right about having repository for tables as opposed to activities or fragments

RxJava2 and Android complex observable chaining

I have been working with Rx Java 2 for awhile but recently came across a situation that has stumped me. I have a semi-complex chain of operations and wish to pass a "state object" down the chain.
There are 4 operations during which I wish to repeat operations 2 and 3 (serialy, not together) until certain conditions are true. I know i can solve this by chaining each operation using andThen(), but this limits my ability to pass a state object down the chain without reaching outside of the chain.
The reason I need to have a state object is because I need to save an initial value during the first operation and compare it to a value recieved during operation 4 to determine if the overall procedure was successful.
Any clues as to what RxJava2 operators can help me achieve the proper repeat conditions for operation 2 and 3? I would prefer to not nest observables if possible.
You can keep your state as some AtomicReference<State> and use repeatUntil operator.
AtomicReference<State> state = new AtomicReference<>();
Completable operation = Completable.create() // do something and modify state
.repeatUntil(() -> state.get() == SATISFYING_CONDITION);
You can easily chain these Completables with andThen

Replace LiveData with RxJava in MVVM artitecture

LiveData seems very useful since it only notifies the view when the view is in an active state. It also stores and returns the last value to new subscribers right after subscribe.
My question is how to achieve the same thing with only RxJava?
Since Rx is a fully functional reactive solution combining it with another reactive solution doesn't seem right. I prefer if I could just remove LiveData from the project.
I know about https://github.com/trello/RxLifecycle and https://github.com/uber/AutoDispose but what they do is unsubscribing from the stream. I don't want that. I want my stream to exist as longs as view model is alive but my activities to start and stop listening to the steam base on lifecycle.
any suggestion will be appreciated
You can definitely do the whole thing with only RxJava. That's what I did with my team in many apps:
Regarding life-cycle management, we do two things:
We use the ViewModel from the Architecture Components to retain the ViewModel's states when the View gets destroyed https://developer.android.com/topic/libraries/architecture/viewmodel
We subscribe to our ViewModel's RxJava-Properties in our View/Fragment and dispose the subscription e.g. in onStop/onDestroy. RxLifecycle as you mentioned is actually of great help there. You cannot or rather should not retain the subscriptions when the View gets destroyed. It creates a memory leak since the subscription still has a hard reference on your View/Fragment. Also, the whole thing will crash if your ViewModel's RxProperties fire when the View was destroyed (e.g. when a server request returns after closing the app). So you need to dispose your subscriptions, no way around that in Android.
As #Samuel mentioned a BehaviourSubject/PublishSubject (depending on the case) is a great fit to provide Inputs to a ViewModel. The Outputs will be Observables which you subscribe to. Your ViewModel could have an interface with Outputs and Inputs like this:
interface TasksViewModel {
// inputs
Observer<Task> taskAddedTrigger();
Observer<Task> taskClickedTrigger();
Observer<Task> taskCompletedTrigger();
// outputs
Observable<Boolean> isLoading();
Observable<List<Task>> tasks();
}
Your ViewModel then just uses RxJava to map inputs to outputs in a very functional style. You Fragment supplies Inputs to the ViewModel whenever User input is received. It subscribes to Outputs and updates the user interface accordingly when the ViewModel's Output changes.
Here is a compact article where I summarize this whole Architecture (MVVM+RxJava)
Let me know if you run into trouble with any details of this approach. I used this architecture in many apps.
You can go with 2 solutions:
first one you can use LiveDataReactiveStreams to convert from a livedata stream to a rx stream and vice versa.
second one you may definitely get rid of livedata and use BehaviourSubject (a rx stream that store last value as live data), but as you said you'll have to subscibe/unsubscribe when your view resume/pause

BehaviorSubject vs PublishSubject

I'm trying to get my head around the golden rule (if any) about:
When to use BehaviorSubject ?
and
When to use PublishSubject ?
The difference between them is very clear
There are many kinds of subjects. For this specific requirement, a PublishSubject works well because we wish to continue the sequence from where it left off. So assuming events 1,2,3 were emitted in (B), after (A) connects back we only want to see 4, 5, 6. If we used a ReplaySubject we would see [1, 2, 3], 4, 5, 6; or if we used a BehaviorSubject we would see 3, 4, 5, 6 etc.
(source : How to think about Subjects in RxJava (Part 1))
I have seen that Subject's are used in two contexts (at least), UI context and listener context.
UI context (MVVM as example)
For example here a BehaviorSubject is used, and it's clear why they use Subject and not Observable but I have changed the BehaviorSubject to PublishSubject but the app behavior still the same.
Listener context
Why they make project field a BehaviorSubject and not PublishSubject ?
The main difference between PublishSubject and BehaviorSubject is that the latter one remembers the last emitted item. Because of that BehaviorSubject is really useful when you want to emit states.
Why they make project field a BehaviorSubject and not PublishSubject ?
Probably because they want to be able to retrieve the last emitted project with this method:
#Override public #NonNull Observable<Project> project() {
return this.project;
}
PublishSubject: Starts empty and only emits new elements to subscribers.
There is a possibility that one or more items may be lost between the time the Subject is created and the observer subscribes to it because PublishSubject starts emitting elements immediately upon creation.
BehaviorSubject: It needs an initial value and replays it or the latest element to new subscribers. As BehaviorSubject always emits the latest element, you can’t create one without giving a default initial value.
BehaviorSubject is helpful for depicting "values over time". For example, an event stream of birthdays is a Subject, but the stream of a person's age would be a BehaviorSubject.
Publish Subject: Here, if a student entered late into the classroom, he just wants to listen from that point of time when he entered the classroom. So, Publish will be the best for this use-case.
Behavior Subject: Here, if a student entered late into the classroom, he wants to listen the most recent things(not from the beginning) being taught by the professor so that he gets the idea of the context. So, here we will use Behavior.
The difference on BehaviourSubject and PublishSubject relies on how long they keep the data they captures, in instance the PublishSubject only keeps the data available at moment and keeps updating on every entry while BehaviourSubject keeps the last data inserted, so you may use for example to confirm password on a signup form and as an example for PublishSubject, performing a search and it has to update the data constantly in order to give accurate results and there's no too much necessity to compare data that are being inserted.
As reference i leave this two photos from http://reactivex.io/documentation/subject.html
PublishSubject
BehaviourSubject

Categories

Resources