I am writing an Android app using Rxjava, the app simply collects sensor data and stores them in files when a buffer is full. For this purpose, I am using a PublishProcessor that emits the value every time a sensor event is detected.
I Have the following helper classes:
interface RxVariableInterface<T,R>{
var value : T
val observable : R
}
//Value is received on subscribing
class ProcessablePublishVariable<T> (defaultValue: T) : RxVariableInterface<T,PublishProcessor<T>>{
override var value: T = defaultValue
set(value) {
field = value
observable.onNext(value)
}
override val observable = PublishProcessor.create<T>()
}
...
var imuProcessablePublishVariable : ProcessablePublishVariable<SensorProto6> = ProcessablePublishVariable(SensorProto6( ... ))
When a sensor event occurs I just do the following:
imuProcessablePublishVariable.value = SensorProto6(...)
In the listener side, I've created an Observer which does the data packing in text files and subscribes with a buffer operator:
class MySubscriber<List<SensorProto6>> : Subscriber<List<SensorProto6>> {
var subscription : Subscription? = null
override fun onError(e: Throwable) {
Log.e("RxJavaHAndlerProcessor","${e.stackTrace}")
}
override fun onSubscribe(s: Subscription) {
subscription = s
subscription!!.request(1)
}
override fun onComplete() { ... }
override fun onNext(t: List<SensorProto6>) {
// Post the buffer list to the writer thread
mWorkerHandler?.post(WriterRunnable(t, mContext,SensorProtos.SensorHeader.SensorType.IMU))
subscription!!.request(1)
}
}
...
imuProcessablePublishVariable.observable
.subscribeOn(Schedulers.io())
.buffer(500)
.observeOn(Schedulers.io())
.subscribe(mySubscriber)
Everything is working as expected, I receive lists of sensor readings containing 500 elements. Is there a way to flush the buffer and emits a partial list?
E.g. the user stops the app when the buffer is 70% full, I would like to retrieve the pending list without waiting for the buffer to be full. Is there another way to implement this functionality?
Related
I want to debounce the items sent to a shared flow, and consume them after that. Something like this:
private var flow = MutableSharedFlow()
suspend fun search(query: String): Flow<Result> {
flow.emit(query)
return flow.debounce(1000).map{ executeSearch(it) }
}
The event that initiates the search is a user writing on a field. For each character, the search function is called. So I want to get a debounced result, to avoid many queries to the server.
It looks like the debounce operator returns a different flow instance each time, so that all the queries end up invoking the executeSearch() function, without dropping any of them as you could expect by using a debounce operator. How can I achieve a functionality like this, so that a client can invoke a function that returns a flow with debounced results?
You can try something like this:
private var flow = MutableSharedFlow()
init {
flow.debounce(1000)
.collect {
val result = executeSearch(it)
// Process the result (maybe send to the UI)
}
}
suspend fun search(query: String) {
flow.emit(query)
}
With two flows you could do it like this. One backing flow takes all the search inputs, and the second is a debounce version of it that runs the query. The search function doesn’t return a flow because the Flow is already available as a property and we aren’t creating new ones for each input.
private val searchInput = MutableSharedFlow<String>()
val searchResults = searchInput.debounce(1000)
.map { executeSearch(it) }
.shareIn(viewModelScope, SharingStarted.Eagerly)
fun submitSearchInput(query: String) {
searchInput.tryEmit(query)
}
You could alternatively do it with jobs that you extinguish when new inputs come in:
private val searchJob: Job? = null
private val _searchResults = MutableSharedFlow<SearchResultType>()
val searchResults = _searchResults.asSharedFlow()
fun submitSearchInput(query: String) {
searchJob?.cancel()
searchJob = viewModelScope.launch {
delay(1000)
_searchResults.emit(executeSearch(query))
}
}
I am developing app using MVVM pattern, Retrofit, and Rx Java. The app is showing library branches and the books available there. When the activity start, I start loading all the branches, and then for every branch, I load it books. something like this in the ViewModel:
private val branchBooksState: MutableLiveData<Pair<Branch,<List<Books>>> = MutableLiveData()
private val libraryBranchesState: MutableLiveData<Outcome<List<Branch>>> = MutableLiveData()
fun libraryBranchesState(): LiveData<Outcome<List<Branch>>> = libraryBranchesState
fun branchBooksState(): LiveData<Pair<Library,<List<Books>>> = branchBooksState
.....
fun loadLibraryBranchesAndThiereBooks(){
compositeDisposable.add(
librariesInteractor.loadAllLibrarybranches()
.map {
libraryBranchesState.postValue(it)
it
}
.toObservable()
.flatMapIterable { it }
.flatMap {
librariesInteractor.loadBranchBooks(it.id).map { bookslist -> Pair(it, bookslist) }.toObservable()
}
.subscribe { pair ->
// there I get pair1, pair2, pair3, pair4
branchBooksState.postValue(pair)
})}
In the Activity, I observe the livedata in the viewModel and update the UI when the data is changed.
Everything works fine but the problem in subscribe when I receive the data from the parallel request and use branchBooksState.postValue(pair) to update livedata obj, the UI is not being notified for every state.
For example, in subscribe I post the values:
Pair 1
Pair 2
Pair 3
Pair 4
but in the activity, I am not notifying with every change. sometimes I notified with:
Pair 1
Pair 3
Pair 4, and pair 2 is missing
.......
private val libraryBranchesState = Observer<List<Branch>> {
// display branches
}
private val branchBooksObserver = Observer<Pair<Branch ,<List<Book>>> {
// problem here. I get pair1 , pair3 , pair4
// display branch books
}
override fun onCreate(savedInstanceState: Bundle?) {
viewModel.libarayBooksState().observe(this, branchBooksObserver)
viewModel.libraryBranchesState().observe(this, libraryBranchesObserver)}
override fun onStart() {
super.onStart()
viewModel.loadLibraryBranchesAndThiereBooks()
}
I am struggling with this. Why the Activity is not notified with every change that happens in the livedata obj. What is wrong with my code? and how to fix it?
There isn't actually anything wrong with the code;
As per the documentation:
If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched. (this refers to postValue)
As LiveData skips intermediate values only the last value should be considered as the truth.
Also if you wish to have all the values, add the value to a List instead and post an immutable List to the LiveData
The function can be changed to
private val yourMutableList = mutableListOf<Pair<Library,<List<Books>>>()
fun loadLibraryBranchesAndThiereBooks(){
compositeDisposable.add(
librariesInteractor.loadAllLibrarybranches()
.map {
libraryBranchesState.postValue(it)
it
}
.toObservable()
.flatMapIterable { it }
.flatMap {
librariesInteractor.loadBranchBooks(it.id).map { bookslist -> Pair(it, bookslist) }.toObservable()
}
.subscribe { pair ->
yourMutableList.add(pair)
branchBooksState.postValue(yourMutableList.toList())
})}
I'm using mvvm and android architecture component , i'm new in this architecture .
in my application , I get some data from web service and show them in recycleView , it works fine .
then I've a button for adding new data , when the user input the data , it goes into web service , then I have to get the data and update my adapter again.
this is my code in activity:
private fun getUserCats() {
vm.getCats().observe(this, Observer {
if(it!=null) {
rc_cats.visibility= View.VISIBLE
pb.visibility=View.GONE
catAdapter.reloadData(it)
}
})
}
this is view model :
class CategoryViewModel(private val model:CategoryModel): ViewModel() {
private lateinit var catsLiveData:MutableLiveData<MutableList<Cat>>
fun getCats():MutableLiveData<MutableList<Cat>>{
if(!::catsLiveData.isInitialized){
catsLiveData=model.getCats()
}
return catsLiveData;
}
fun addCat(catName:String){
model.addCat(catName)
}
}
and this is my model class:
class CategoryModel(
private val netManager: NetManager,
private val sharedPrefManager: SharedPrefManager) {
private lateinit var categoryDao: CategoryDao
private lateinit var dbConnection: DbConnection
private lateinit var lastUpdate: LastUpdate
fun getCats(): MutableLiveData<MutableList<Cat>> {
dbConnection = DbConnection.getInstance(MyApp.INSTANCE)!!
categoryDao = dbConnection.CategoryDao()
lastUpdate = LastUpdate(MyApp.INSTANCE)
if (netManager.isConnected!!) {
return getCatsOnline();
} else {
return getCatsOffline();
}
}
fun addCat(catName: String) {
val Category = ApiConnection.client.create(Category::class.java)
Category.newCategory(catName, sharedPrefManager.getUid())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ success ->
getCatsOnline()
}, { error ->
Log.v("this", "ErrorNewCat " + error.localizedMessage)
}
)
}
private fun getCatsOnline(): MutableLiveData<MutableList<Cat>> {
Log.v("this", "online ");
var list: MutableLiveData<MutableList<Cat>> = MutableLiveData()
list = getCatsOffline()
val getCats = ApiConnection.client.create(Category::class.java)
getCats.getCats(sharedPrefManager.getUid(), lastUpdate.getLastCatDate())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ success ->
list += success.cats
lastUpdate.setLastCatDate()
Observable.just(DbConnection)
.subscribeOn(Schedulers.io())
.subscribe({ db ->
categoryDao.insert(success.cats)
})
}, { error ->
Log.v("this", "ErrorGetCats " + error.localizedMessage);
}
)
return list;
}
I call getCat from activity and it goes into model and send it to my web service , after it was successful I call getCatsOnline method to get the data again from webservice .
as I debugged , it gets the data but it doesn't notify my activity , I mean the observer is not triggered in my activity .
how can I fix this ? what is wrong with my code?
You have made several different mistakes of varying importance in LiveData and RxJava usage, as well as MVVM design itself.
LiveData and RxJava
Note that LiveData and RxJava are streams. They are not one time use, so you need to observe the same LiveData object, and more importantly that same LiveData object needs to get updated.
If you look at getCatsOnline() method, every time the method gets called it's creating a whole new LiveData instance. That instance is different from the previous LiveData object, so whatever that is listening to the previous LiveData object won't get notified to the new change.
And few additional tips:
In getCatsOnline() you are subscribing to an Observable inside of another subscriber. That is common mistake from beginners who treat RxJava as a call back. It is not a call back, and you need to chain these calls.
Do not subscribe in Model layer, because it breaks the stream and you cannot tell when to unsubscribe.
It does not make sense to ever use AndroidSchedulers.mainThread(). There is no need to switch to main thread in Model layer especially since LiveData observers only run on main thread.
Do not expose MutableLiveData to other layer. Just return as LiveData.
One last thing I want to point out is that you are using RxJava and LiveData together. Since you are new to both, I recommend you to stick with just one of them. If you must need to use both, use LiveDataReactiveStreams to bridge these two correctly.
Design
How to fix all this? I am guessing that what you are trying to do is to:
(1) view needs category -> (2) get categories from the server -> (3) create/update an observable list object with the new cats, and independently keep the result in DB -> (4) list instance should notify activity automatically.
It is difficult to pull this off correctly because you have this list instance that you have to manually create and update. You also need to worry about where and how long to keep this list instance.
A better design would be:
(1) view needs category -> (2) get a LiveData from DB and observe -> (3) get new categories from the server and update DB with the server response -> (4) view is notified automatically because it's been observing DB!
This is much easier to implement because it has this one way dependency: View -> DB -> Server
Example CategoryModel:
class CategoryModel(
private val netManager: NetManager,
private val sharedPrefManager: SharedPrefManager) {
private val categoryDao: CategoryDao
private val dbConnection: DbConnection
private var lastUpdate: LastUpdate // Maybe store this value in more persistent place..
fun getInstance(netManager: NetManager, sharedPrefManager: SharedPrefManager) {
// ... singleton
}
fun getCats(): Observable<List<Cat>> {
return getCatsOffline();
}
// Notice this method returns just Completable. Any new data should be observed through `getCats()` method.
fun refreshCats(): Completable {
val getCats = ApiConnection.client.create(Category::class.java)
// getCats method may return a Single
return getCats.getCats(sharedPrefManager.getUid(), lastUpdate.getLastCatDate())
.flatMap { success -> categoryDao.insert(success.cats) } // insert to db
.doOnSuccess { lastUpdate.setLastCatDate() }
.ignoreElement()
.subscribeOn(Schedulers.io())
}
fun addCat(catName: String): Completable {
val Category = ApiConnection.client.create(Category::class.java)
// newCategory may return a Single
return Category.newCategory(catName, sharedPrefManager.getUid())
.ignoreElement()
.andThen(refreshCats())
.subscribeOn(Schedulers.io())
)
}
}
I recommend you to read through Guide to App Architecture and one of these livedata-mvvm example app from Google.
I have a problem with getting the last emitted value from the Subject
This is my class which is responsible for emitting and observing the battery changes:
class BatteryLevelProvider #Inject constructor(
app: App
) {
private val context: Context = app
private val receiver: PowerConnectionReceiver = PowerConnectionReceiver()
init {
initializeReceiver()
}
private fun initializeReceiver() {
IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { intentFilter ->
context.registerReceiver(receiver, intentFilter)
}
}
companion object {
val batteryLevelSubject = PublishSubject.create<Int>()
}
fun observeBatteryLevel(): Observable<Int> = batteryLevelSubject.distinctUntilChanged()
fun getCurrentBatteryLevel(): Int {
Timber.d("getCurrentBatteryLevel: ENTERED")
val blockingLast = batteryLevelSubject.blockingLast(0)
Timber.d("getCurrentBatteryLevel: $blockingLast")
return blockingLast
}
inner class PowerConnectionReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val level: Int = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val scale: Int = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
val percentage= (level / scale.toFloat() * 100).toInt()
batteryLevelSubject.onNext(percentage)
Timber.d("Battery changed: $percentage")
}
}
}
When i invoke the
getCurrentBatteryLevel()
It reach the blockingLast never get return the value and hangs the app.
What is the reason and how to handle this properly?
subject.blockingLast(0) means the following: get the last value after the stream has completed emitting values and if it has completed without emitting anything then return the default value.
That means that blockingLast will wait until it receives onComplete event because only then it can figure out that the stream has ended (and emit last value). PublishSubject creates an infinite stream and you never call batteryLevelSubject.onComplete to end the stream manually and that's why it hangs forever.
You can easily fix that by changing PublishSubject to BehaviorSubject. The main difference between them is that BehaviorSubject caches the last received value which can then be received by anyone. Also, you need to change batteryLevelSubject.blockingLast(0) to batteryLevelSubject.value to get the last cached value (and it won't block anything!). But be aware that the value may be null at the first run when you haven't put there anything yet. You can easily fix that by creating BehaviorSubject with default value like so:
val subject = BehaviorSubject.createDefault(0)
I have remote service to which the app have to send data:
Definition in retrofit2:
interface FooRemoteService {
#POST("/foos")
fun postFoos(#Body foos: List<FooPojo>): Observable<Response<List<String>>
}
but the call has a limits no more than X Foos at once.
Each call can returns 206 code "partially successful" with list of unsuccessful uploaded foos. Also 413 "Request Entity Too Large". And of course 400 and 500 as well.
And the app needs to send unknown count of foo items (defined by user in runtime).
To avoid DDoS of service app is required to send this calls one by one.
So I made such implementation in my FooRepositoryImpl:
This is an idea. I'm not happy with below solution and I'm sure that it can be done much better but I'm run out of ideas. So any proposes?
override fun postFoos(foos: List<Foo>) Completable {
val fooChunks = divideListInToChuncksUnderRequestLimit(foos)
val unuploadedFoos = mutableListOf<UnuploadedFoo>()
fooChunks.fold(unuploadedFoos)
{ accu: MutableList<UnuploadedFoo>, chunk ->
fooRemoteService
.postFoos(chunk)
.subscribeOn(Schedulers.io())
.flatMapCompletable {
if (it.isSuccessful) {
Completable.complete()
} else {
Timber.e("$it")
accu.add(it.body())
}
}.blockingAwait()
responses
}
return Completable.complete()
}
At the end the app should display list of all unsuccessful foos or if any available. So I need pass from that fuction list of unuploaded Foos.
If you are OK with modifying the return type of postFoos a bit, something like this could work:
override fun postFoos(foos: List<Foo>): Observable<List<UnuploadedFoo>> {
val chunks = foos.chunked(CHUNK_SIZE)
val posters = chunks.map { chunk ->
fooRemoteService.postFoos(chunk)
.map { response ->
response.unUploaded.takeIf { !response.isSuccessful } ?: emptyList()
}
.filter { it.isNotEmpty() }
.toObservable()
}
return Observable.concatDelayError(posters)
}
I'm imagining your service to have something like:
data class Response(val isSuccessful: Boolean, val unUploaded: List<UnoploadedFoo>)
fun postFoos(foos: List<Foo>): Single<Response>
The trick here is that Concat:
(...) waits to subscribe to each additional Observable that you pass to it until the previous Observable completes.