Send data from repository to view model in MVVM architecture - android

I'm confused about using MVVM architecture! In some tutorials, LiveData is stored in a repository and then passed to the ViewModel. In many others, LiveData is defined within a function of repository and passed to the ViewModel using the result of the function. But Google says:
It may be tempting to work LiveData objects in your data layer class, but LiveDatais not designed to handle asynchronous streams of data. ........ If you need to use streams of data in other layers of your app, consider using Kotlin Flows and then converting them to LiveData in the ViewModel using asLiveData(). .... For codebases built with Java, consider using Executors in conjuction with callbacks or RxJava.
I prefer to use Kotlin coroutines or Flows. But I do not know if in my case is really needed or not. I'm working on a chat application. When a message is received, a listener is called in the repository and the message data is received in it. (The listener runs in the background thread) Now I want to send the message object to the ViewModel and add it to a LiveData that stores the list of messages.
object ChatsRepo {
fun listen(socket: Socket) {
socket.on(Config.ON_MESSAGE, onMessage)
}
fun sendMessage(socket: Socket, json: String) {
socket.emit(Config.ON_MESSAGE, json)
}
private val onMessage = Emitter.Listener { args: Array<Any> ->
//This message object must be sent to ViewModel
val message = Gson().fromJson(args[0].toString(), Message::class.java)
}
}
I can easily do this using the higher-order function:
object ChatsRepo {
lateinit var listener: (Message) -> Unit
private val onMessage = Emitter.Listener { args: Array<Any> ->
val message = Gson().fromJson(args[0].toString(), Message::class.java)
listener(message)
}
}
But is it better to use Kotlin coroutines or Flows? In some similar cases, a list needs to be sent to the ViewModel.

I can easily do this using the higher-order function.
Right, this is a callback you can use to notify ViewModel about new messages. Kotlin Coroutines help to avoid callbacks and to have a sequential code.
In your case the onMessage is a hot stream of data, we can convert it to a hot Flow using SharedFlow:
private val _messagesFlow = MutableSharedFlow<Message>(extraBufferCapacity = 64)
val messagesFlow: SharedFlow<Message> = _messagesFlow
private val onMessage = Emitter.Listener { args: Array<Any> ->
val message = Gson().fromJson(args[0].toString(), Message::class.java)
messagesFlow.tryEmit(message)
}
In ViewModel if need it is easy to convert it to LiveData using method asLiveData:
ChatsRepo.messagesFlow.asLiveData()
Dependency to use asLiveData() extension function:
def lifecycle_version = "2.4.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"

If you are a Rxjava master, I will not recommend you to use flow/livedata. I think flow/livedata is designed for UI Reactive, not for underlying data transform. So you can use rxjava in data repository, and in viewmodel, you can convert it to livedata and use it.

Livedata was never design for reactive streams it was always been for last layer (viewmodel to view), there were/are workaround when livedata was use with retrofit(using calladapter) and room(which google did it).
And now since the rise of coroutines recommended ways is to use kotlin Flows when dealing with business logic but things to be notice:
1- regular flow are not observables but livedata is observable.
2- it's not good to manage UI state with regular flows but you can do it with livedata.
3- livedata is lifecycle aware but flows are not.
4- livedata is not reactive but flows are.
5- livedata gives you the only latest value what it receives but regular flows throws values from bucket one by one
you might have notice i said alot of regular flows not flow cause there are other mechanism related to flows you can manage UI state that are stateFlow and sharedFlow.
Stateflow is the replacement of livedata the downside is you have to code a little bit more to make it lifecycle aware cause it is not prebuilt and stateflow always gives you the last value it persists during the configuration changes or screen switching(fragment navigation).
sharedFlows is good for managing the one time event like toastMsgs, snackbar etc. sharedFlow was actually the replacement of BroadcastChannels.

Related

Map multiple suspend functions to single LiveData

The company I just started working at uses a so called Navigator, which I for now interpreted as a stateless ViewModel. My Navigator receives some usecases, with each contains 1 suspend function. The result of any of those usecases could end up in a single LiveData. The Navigator has no coroutine scope, so I pass the responsibility of scoping suspending to the Fragment using fetchValue().
Most current code in project has LiveData in the data layer, which I tried not to. Because of that, their livedata is linked from view to dao.
My simplified classes:
class MyFeatureNavigator(
getUrl1: getUrl1UseCase,
getUrl1: getUrl1UseCase
) {
val url = MediatorLiveData<String>()
fun goToUrl1() {
url.fetchValue { getUrl1() }
}
fun goToUrl2() {
url.fetchValue { getUrl2() }
}
fun <T> MediatorLiveData<T>.fetchValue(provideValue: suspend () -> T) {
val liveData = liveData { emit(provideValue()) }
addSource(liveData) {
removeSource(liveData)
value = it
}
}
}
class MyFeatureFragment : Fragment {
val viewModel: MyFeatureViewModel by viewModel()
val navigator: MyFeatureNavigator by inject()
fun onViewCreated() {
button.setOnClickListener { navigator.goToUrl1() }
navigator.url.observe(viewLifecycleOwner, Observer { url ->
openUrl(url)
})
}
}
My two questions:
Is fetchValue() a good way to link a suspend function to LiveData? Could it leak? Any other concerns?
My main reason to only use coroutines (and flow) in the data layer, is 'because Google said so'. What's a better reason for this? And: what's the best trade off in being consistent with the project and current good coding practices?
Is fetchValue() a good way to link a suspend function to LiveData?
Could it leak? Any other concerns?
Generally it should work. You probably should remove the previous source of the MediatorLiveData before adding new one, otherwise if you get two calls to fetchValue in a row, the first url can be slower to fetch, so it will come later and win.
I don't see any other correctness concerns, but this code is pretty complicated, creates a couple of intermediate objects and generally difficult to read.
My main reason to only use coroutines (and flow) in the data layer,
is 'because Google said so'. What's a better reason for this?
Google has provided a lot of useful extensions to use coroutines in the UI layer, e.g. take a look at this page. So obviously they encourage people to use it.
Probably you mean the recommendation to use LiveData instead of the Flow in the UI layer. That's not a strict rule and it has one reason: LiveData is a value holder, it keeps its value and provides it immediately to new subscribers without doing any work. That's particularly useful in the UI/ViewModel layer - when a configuration change happens and activity/fragment is recreated, the newly created activity/fragment uses the same view model, subscribes to the same LiveData and receives the value at no cost.
At the same time Flow is 'cold' and if you expose a flow from your view model, each reconfiguration will trigger a new flow collection and the flow will be to execute from scratch.
So e.g. if you fetch data from db or network, LiveData will just provide the last value to new subscriber and Flow will execute the costly db/network operation again.
So as I said there is no strict rule, it depends on the particular use-case. Also I find it very useful to use Flow in view models - it provides a lot of operators and makes the code clean and concise. But than I convert it to a LiveData with help of extensions like asLiveData() and expose this LiveData to the UI. This way I get best from both words - LiveData catches value between reconfigurations and Flow makes the code of view models nice and clean.
Also you can use latest StateFlow and SharedFlow often they also can help to overcome the mentioned Flow issue in the UI layer.
Back to your code, I would implement it like this:
class MyFeatureNavigator(
getUrl1: getUrl1UseCase,
getUrl1: getUrl1UseCase
) {
private val currentUseCase = MutableStateFlow<UseCase?>(null)
val url = currentUseCase.filterNotNull().mapLatest { source -> source.getData()}.asLiveData()
fun goToUrl1() {
currentUseCase.value = getUrl1
}
fun goToUrl2() {
currentUseCase.value = getUrl2
}
}
This way there are no race conditions to care about and code is clean.
And: what's the best trade off in being consistent with the project
and current good coding practices?
That's an arguable question and it should be primarily team decision. In most projects I participated we adopted this rule: when fixing bugs, doing maintenance of existing code, one should follow the same style. When doing big refactoring/implementing new features one should use latest practices adopted by the team.

Using emit with Kotlin StateFlow

Until now I was using Flow and mapping it to LiveData like below -
The MyService looks like this -
override fun provideData(action: MyAction) = flow {
emit(MyResult.Loading)
emit(dataRepository.getNewData())
}
The ViewModel looks like this -
fun getData() = myService.provideData(MyAction.GetData).map {
}.asLiveData(Dispatchers.Default + viewModelScope.coroutineContext)
I want to move to StateFlow. How can I use emit function with StateFlow like I used it with Flow.
You can write your flow as before, but replace .asLiveData(scope) with .stateIn(scope, SharingStarted.Eagerly, null) to get an instance of StateFlow running in the corresponding scope with a similar behavior that you were getting with LiveData before — sharing is started immediately and the initial value is null (just like with LiveData).
You can read here for more details and explanation of all stateIn operator parameters here https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/state-in.html

Kotlin flow (or something similar) that can be collected with multiple collectors

I tried using Kotlin Flow to be some kind of message container which should pass this message to all observers (collectors). I do not want to use LiveData on purpose because it need to be aware of lifecycle.
Unfortunately I have noticed that if one collector collects message from flow no one else can receive it.
What could I use to achieve "one input - many output".
You can use StateFlow or SharedFlow, they are Flow APIs that enable flows to optimally emit state updates and emit values to multiple consumers.
From the documentation, available here:
StateFlow: is a state-holder observable flow that emits the current and new state updates to its collectors. The current state value can also be read through its value property.
SharedFlow: a hot flow that emits values to all consumers that collect from it. A SharedFlow is a highly-configurable generalization of StateFlow.
A simple example using state flow with view model:
class myViewModel() : ViewModel() {
val messageStateFlow = MutableStateFlow("My inicial awesome message")
}
You can emit a new value using some scope:
yourScope.launch {
messageStateFlow.emit("My new awesome message")
}
You can collect a value using some scope:
yourScope.launch {
messageStateFlow.collect {
// do something with your message
}
}
Attention: Never collect a flow from the UI directly from launch or the launchIn extension function to update UI. These functions process events even when the view is not visible. You can use repeatOnLifecycle as the documentation sugests.
You can try BehaviorSubject from rxJava. Is more comfortable to use than poor kotlin.flow. Seems like this link is for you: BehaviorSubject vs PublishSubject
val behaviorSubject = BehaviorSubject.create<MyObject> {
// for example you can emit new item with it.onNext(),
// finish with error like it.onError() or just finish with it.onComplete()
somethingToEmit()
}
behaviorSubject.subscribe {
somethingToHandle()
}

How to get the value of a Flow outside a coroutine?

How can I get the value of a Flow outside a coroutine similarly to LiveData?
// Suspend function 'first' should be called only from a coroutine or another suspend function
flowOf(1).first()
// value is null
flowOf(1).asLiveData().value
// works
MutableLiveData(1).value
Context
I'm avoiding LiveData in the repository layer in favor of Flow. Yet, I need to set, observe and collect the value for immediate consumption. The later is useful for authentication purpose in a OkHttp3 Interceptor.
You can do this
val flowValue: SomeType
runBlocking(Dispatchers.IO) {
flowValue = myFlow.first()
}
Yes its not exactly what Flow was made for.
But its not always possible to make everything asynchronous and for that matter it may not even always be possible to 'just make a synchronous method'. For instance the current Datastore releases (that are supposed to replace shared preferences on Android) do only expose Flow and nothing else. Which means that you will very easiely get into such a situation, given that none of the Lifecycle methods of Activities or Fragments are coroutines.
If you can help it you should always call coroutines from suspend functions and avoid making runBlocking calls. A lot of the time it works like this. But it´s not a surefire way that works all the time. You can introduce deadlocks with runBlocking.
Well... what you're looking for isn't really what Flow is for. Flow is just a stream. It is not a value holder, so there is nothing for you retrieve.
So, there are two major avenues to go down, depending on what your interceptor needs.
Perhaps your interceptor can live without the data from the repository. IOW, you'll use the data if it exists, but otherwise the interceptor can continue along. In that case, you can have your repository emit a stream but also maintain a "current value" cache that your interceptor can use. That could be via:
BroadcastChannel
LiveData
a simple property in the repository that you update internally and expose as a val
If your interceptor needs the data, though, then none of those will work directly, because they will all result in the interceptor getting null if the data is not yet ready. What you would need is a call that can block, but perhaps evaluates quickly if the data is ready via some form of cache. The details of that will vary a lot based on the implementation of the repository and what is supplying the Flow in the first place.
You could use something like this:
fun <T> SharedFlow<T>.getValueBlockedOrNull(): T? {
var value: T?
runBlocking(Dispatchers.Default) {
value = when (this#getValueBlockedOrNull.replayCache.isEmpty()) {
true -> null
else -> this#getValueBlockedOrNull.firstOrNull()
}
}
return value
}
You can use MutableStateFlow and MutableSharedFlow for emitting the data from coroutine and receiving the data inside Activity/Fragment. MutableStateFlow can be used for state management. It requires default value when initialised. Whereas MutableSharedFlow does not need any default value.
But, if you don't want to receive stream of data, (i.e) your API call sends data only once, you can use suspend function inside coroutine scope and the function will perform the task and return the result like synchronous function call.
To get the value of a Flow outside of a coroutine, the best option is to create the flow as a StateFlow and then call the value property on the StateFlow.
class MyClass {
private val mutableProperty = MutableStateFlow(1)
val property = mutableProperty.asStateFlow()
...
mutableProperty.value = 2
}
...
val readProperty = MyClass().property.value
val propertyAsFlow = MyClass().property as Flow<Int>

Kotlin Flow vs LiveData

In the last Google I/O, Jose Alcerreca and Yigit Boyar told us that we should no longer use LiveData to fetch data. Now we should use suspend functions for one-shot fetches and use Kotlin's Flow to create a data stream. I agree that coroutines are great for one-shot fetching or other CRUD operations, such as inserting, etc. But in cases where I need a data stream, I don’t understand what advantages Flow gives me. It seems to me that LiveData is doing the same.
Example with Flow:
ViewModel
val items = repository.fetchItems().asLiveData()
Repository
fun fetchItems() = itemDao.getItems()
Dao
#Query("SELECT * FROM item")
fun getItems(): Flow<List<Item>>
Example with LiveData:
ViewModel
val items = repository.fetchItems()
Repository
fun fetchItems() = itemDao.getItems()
Dao
#Query("SELECT * FROM item")
fun getItems(): LiveData<List<Item>>
I would also like to see some examples of projects using coroutines and Flow to work with the Room or Retrofit. I found only a Google's ToDo sample where coroutines are used for one-shot fetching and then manually refetch data on changing.
Flow is sort of a reactive stream ( like rxjava ). There are a bunch of different operators like .map, buffer() ( anyway less no. Of operator compared to rxJava ). So, one of the main difference between LiveData and Flow is that u can subscribe the map computation / transformation in some other thread using
flowOn(Dispatcher....).
So, for eg :-
flowOf("A","B","C").map { compute(it) }.flowOn(Dispatchers.IO).collect {...} // U can change the execution thread of the computation ( by default its in the same dispatcher as collect )
With LiveData and map , the above can't be achieved directly !
So its recommended to keep flow in the repository level , and make the livedata a bridge between the UI and the repository !
The main difference is that
Generally a regular flow is not lifecycle aware but liveData is lifecyle aware. ( we can use stateFlow in conjunction with repeatOnLifecycle to make it lifecycle aware )
flow has got a bunch of different operators which livedata doesn't have !
But again , Its up to u how do u wanna construct your project !
As the name suggests, you can think of Flow like a continuous flow of multiple asynchronously computed values. The main difference between LiveData and Flow, from my point of view, is that a Flow continuously emits results while LiveData will update when all the data is fetched and return all the values at once. In your example you are fetching single values, which is not exactly what Flow was dsigned for [update: use StateFlow for that].
I don't have a Room example but let's say you are rendering something that takes time, but you wanna display results while rendering and buffering the next results.
private fun render(stuffToPlay: List<Any>): Flow<Sample> = flow {
val sample = Sample()
// computationally intensive operation on stuffToPlay
Thread.sleep(2000)
emit(sample)
}
Then in your 'Playback' function you can for example display the results where stuffToPlay is a List of objects to render, like:
playbackJob = GlobalScope.launch(Dispatchers.Default) {
render(stuffToPlay)
.buffer(1000) // tells the Flow how many values should be calculated in advance
.onCompletion {
// gets called when all stuff got played
}
.collect{sample ->
// collect the next value in the buffered queue
// e.g. display sample
}
}
An important characteristic of Flow is that it's builder code (here render function) only gets executed, when it gets collected, hence its a cold stream.
You can also refer to the docs at Asynchronous Flow
Considering that Flow is part of Kotlin and LiveData is part of the androidx.lifecycle library, I think that Flow is used as part of the uses cases in clean architecture (without dependencies to the framework).
LiveData, on the other hand, is lifecycle aware, so is a match with ViewModel
I have all my architecture using livedata at this moment, but Flow looks like an interesting topic to study and adopt.

Categories

Resources