I am trying to use Realm in a Kotlin based application.
I set up a basic project, which tested adding elements into realm, and observing them with live data and a view model.
Then I wanted to add in some other observables, and use combineLatest of RxJava to combine them.
I created Rx Observables for all observables. I observe the live data from Realm, and then call RxJava BehaviourSubject's onNext() method to set one of the Rx Observables to be the same value as the observed realm data. This works fine, but as soon as I added in a Throttle to the Rx Operators, I got the following error message:
Realm access from incorrect thread.
Realm objects can only be accessed on the thread they were created.
at io.realm.BaseRealm.checkIfValid(BaseRealm.java:442)
at io.realm.com_example_myapplication_ItemRealmProxy.realmGet$id(com_example_myapplication_ItemRealmProxy.java:105)
at io.realm.com_example_myapplication_ItemRealmProxy.toString(com_example_myapplication_ItemRealmProxy.java:661)
This is parts of my code:
private val itemViewModel: ItemViewModel by lazy {
ViewModelProviders.of(this).get(ItemViewModel::class.java)
}
val itemsSubject = BehaviorSubject.createDefault(listOf<Item>())
val intsSubject = BehaviorSubject.createDefault(4)
itemViewModel.getItemData().observe(this, Observer<RealmResults<Item>> { t ->
val items = t.map {
it
}
itemsSubject.onNext(items)
})
Observables.combineLatest(itemsSubject, intsSubject) { a, b ->
a
}.throttleLast(500, TimeUnit.MILLISECONDS).subscribe {
Log.i("Items", "Items combined read: ${it}")
}
I can't see how realm is coupled to the onNext call, nor the throttle. Seems very weird.
Ideally I would like to stick to just Live data, but I need to do operations with other observables, and RxJava is good for this.
I needed to observe the RxJava on the main thread, as follows:
Observables.combineLatest(itemsSubject, intsSubject) { a, b ->
a
}.throttleLast(500, TimeUnit.MILLISECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe {
Log.i("Items", "Items combined read: ${it}")
}
}
In a sense, the Realm error was misleading, because it was ultimately an incorrect use of RxJava.
Related
I am new with kotlin flow and I am working about this document. Kotlin Flows. In this code every five seconds datasource fetch data from api and emits it.
This is my example datasource class.
I am getting data and emitting it.
class RemoteDataSourceImpl #Inject constructor(
private val api:CryptoApi
): RemoteDataSource {
override suspend fun cryptoList(): Flow<List<CryptoCoinDto>> {
return flow {
while (true){
val data = api.getCoinList()
emit(data)
delay(5000L)
}
}
}
}
This is my example repository.
I am mapping data and saving it room database. I want to get data from room database and emit it because of single source of truth principle but I still have to return dataSource because if I open new flow{} I can't reach datasource's data. Of course I can fix the problem by using List instead of Flow<List> inside of RemoteDataSource class. But I want to understand this example. How can I apply here single source of truth.
class CoinRepositoryImpl #Inject constructor(
private val dataSource:RemoteDataSource,
private val dao: CryptoDao
):CoinRepository {
override fun getDataList(): Flow<List<CryptoCoin>> {
dataSource.cryptoList().map { dtoList ->
val entityList = dtoList.map { dto ->
dto.toCryptoEntity()
}
dao.insertAll(entityList)
}
return dataSource.cryptoList().map {
it.map { it.toCryptoCoin() }
}
}
This is actually more complicated than it seems. Flows were designed to support back-pressure which means that they usually only produce items on demand, when being consumed. They are passive, instead of pushing items, items are pulled from the flow.
(Disclaimer: this is all true for cold flows, not for hot flows. But cryptoList() is a cold flow.)
It was designed this way to greatly simplify cases when the consumer is slower than producer or nobody is consuming items at all. Then producer just stops producing and everything is fine.
In your case there are two consumers, so this is again more complicated. You need to decide what should happen if one consumer is slower than the other. For example, what should happen if nobody collects data from getDataList()? There are multiple options, each requires a little different approach:
Stop consuming the source flow and therefore stop updating the database.
Update the database all the time and queue items if nobody is collecting from getDataList(). What if there are more and more items in the queue?
Update the database all the time and discard items if nobody is collecting from getDataList().
Ad.1.
It can be done by using onEach():
return dataSource.cryptoList().onEach {
// update db
}.map {
it.map { it.toCryptoCoin() }
}
In this solution updating the database is a "side effect" of consuming the getDataList() flow.
Ad.2. and Ad.3.
In this case we can't passively wait until someone asks us for an item. We need to actively consume items from the source flow and push them to the downstream flow. So we need a hot flow: SharedFlow. Also, because we remain the active side in this case, we have to launch a coroutine that will do this in the background. So we need a CoroutineScope.
Solution depends on your specific needs: do you need a queue or not, what should happen if queue exceeded the size limit, etc., but it will be similar to:
return dataSource.cryptoList().onEach {
// update db
}.map {
it.map { it.toCryptoCoin() }
}.shareIn(scope, SharingStarted.Eagerly)
You can also read about buffer() and MutableSharedFlow - they could be useful to you.
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.
I am using a lot of LiveData in my projects and it's great in those cases where I need to pass something to views since it's intention is to be observed by lifecycle owners (i.e. views).
But I wonder what should I use in those cases when I need to apply some logic in my view models every time when some data from DB changes?
I am familiar with Transformations (map and switch) but (if I am right) they are just a way to transform liveData objects, not a place where I can execute some viewmodel's logic.
If I understand correctly, observing LiveData in viewModels is bad practice.
What is an alternative? Some of the RxJava observable types? Or something else?
"they are just a way to transform liveData objects, not a place where I can execute some viewmodel's logic."
Yes you're right. It's because:
Functions in both map() and switchMap() run on the Main Thread,
so no long running operations should be done there!
But I don't think observing LiveData in ViewModel is bad practice, because in Android we have MediatorLiveData for this purpose. If you take a look at source code of map and switchMap function, you'll see they use MediatorLiveData in there.
So the only problem here is that if some logic you want to execute is a long running task, you must run it in background thread when observe changes from the source LiveData. You can use Rx or something to run it in background thread like below:
private val _downloadState = MutableLiveData<DownloadDataState>()
val downloadState: LiveData<DownloadDataState> = _downloadState
// version observe changes in downloadState
val version = MediatorLiveData<String>().apply {
addSource(downloadState) {
// Whenever value of downloadState changes, this block will run your logic
Single.just(dataRepository.fetchDataVersion())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result ->
// Then set value to the observer
value = result
},
{ e: Throwable ->
e.printStackTrace()
}
)
}
}
In my application I'm using RxJava2 and new class from Architecture Components ViewModel. In my case, I need to push SQL clause to ViewModel, which will do some magic and return Observable that will give me the data I need. Everything works fine, but I am not sure if I am using RX in the best way.
My data flow:
ViewModel has PublishSubject on which I am pushing SQL's. ViewModel has also Observable which is created by mapping subject. Also, I used distinctUntilChanged on Subject, to prevent from executing the same query again.
To cache data I used replay(1).autoconnect(1) on Observable, but that approach had a flaw. Sometimes my Subject pushed Sql when Observable wasn't yet connect, and my data never arrived to me. Should I use BehaviourSubject? Or maybe I shouldn't use replay(1).autoconnect(1) in the first place? Or maybe my whole flow is wrong? Example:
val listSubject: Subject<RawSql> = PublishSubject.create()
val sqlListEmitter: Observable<List<T>> =
listSubject
.subscribeOn(Schedulers.computation())
.map { // SOME MAGIC HERE }
.replay(1).autoConnect(1, { compositeDisposable.add(it) })
In your case autoConnect() just waits for the first subscription to connect() to your stream. Since your subject and your stream build an inherent entity, you might not want to wait for it at all and instead connect it directly.
val listSubject: Subject<RawSql> = PublishSubject.create()
val sqlListEmitter: Observable<List<T>> =
listSubject
.observeOn(Schedulers.computation())
.map { // SOME MAGIC HERE }
.replay(1)
.let {
it.connect(compositeDisposable::add)
it.publish()
}
Also you might need to change subscribeOn() to observeOn(). The subject emits on the same thread as the data is pushed to it and does not consider the thread it's subscribed on.
I'm trying to figure out a way to save my Parsed objects from retro into realm through rxjava observable.
Right now I have an API call:
fetchCatalogData().subscribeOn(Schedulers.io())
.subscribe(data ->
saveToRealm();
)
This doesn't work because either I'm using realm on different treads or that scheduler.io doesn't have a looper.
I'm not sure what's the best way to work with retrofit, realm and rxjava. I want to be able to save all my data when it arrives from retrofit and then access it afterwards.
You may leverage a very good library that combines all.
https://github.com/FabianTerhorst/ApiClient
"An easy to use api client that combines the power of Retrofit, Realm, Gson, Rxjava and Retrolambda in a easy to use library for Java and Android"
It's actually rather simple, all you need to do is have two different streams - one for the download, and one for displaying the data.
I'm assuming fetchCatalogData() returns the data via Retrofit, therefore unmanaged objects.
fetchCatalogData()
.subscribeOn(Schedulers.io())
.subscribe(data ->
saveToRealm(data);
)
In this case this works just fine, as long as saveToRealm() opens a thread-specific Realm-instance:
private void saveToRealm(CatalogData data) {
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction((realm) -> {
realm.insertOrUpdate(data);
});
}
}
This would allow you to create a Realm (which gets auto-closed) on the background thread, and persist to the Realm.
For the UI thread, you would create a query for CatalogData, and via RxJava-support of Realm you get a RealmChangeListener appended to the result set, and receive a "hot Observable" which gives you the results whenever the underlying table changes.
Subscription showCatalog = realm.where(CatalogData.class)
.findAllAsync()
.filter(RealmResults::isLoaded)
.filter(RealmResults::isValid)
.subscribe((results) -> {
adapter.updateData(results);
});
Realm works with both Retrofit 1.* and 2.* out of the box, but note that Retrofit does not automatically add objects to Realm, instead you must manually add them using the realm.copyToRealm() or realm.copyToRealmOrUpdate() methods.
GitHubService service = restAdapter.create(GitHubService.class);
List<Repo> repos = service.listRepos("octocat");
// Copy elements from Retrofit to Realm to persist them.
realm.beginTransaction();
List<Repo> realmRepos = realm.copyToRealmOrUpdate(repos);
realm.commitTransaction();
Also Realm has first-class support for RxJava, which means that the following Realm classes can be exposed as an Observable: Realm, RealmResults, RealmObject, DynamicRealm and DynamicRealmObject.
// Combining Realm, Retrofit and RxJava (Using Retrolambda syntax for brevity)
// Load all persons and merge them with their latest stats from GitHub (if they have any)
Realm realm = Realm.getDefaultInstance();
GitHubService api = retrofit.create(GitHubService.class);
realm.where(Person.class).isNotNull("username").findAllAsync().asObservable()
.filter(persons.isLoaded)
.flatMap(persons -> Observable.from(persons))
.flatMap(person -> api.user(person.getGithubUserName())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(user -> showUser(user));
This this official Realm docs.