I'm converting a very complex write/read/write cycle written on the native BLE stack and am wondering if the following pattern is feasible for handling in RxAndroidBLE (code is Kotlin)
fun sendCommandList(connection: RxBleConnection, commands: Array<String>) {
Observable.fromArray(commands)
.flatMapIterable { it.asIterable() } // convert to individual commands
.map { it.toByteArray() }
.map {
connection.writeCharacteristic(TX_CHAR_UUID, it)
.flatMap { bytes -> connection.readCharacteristic((RX_CHAR_UUID)) }
.flatMap { bytes -> val ackNackBytes = processResponse(bytes); connection.writeCharacteristic(TX_CHAR_UUID, ackNackBytes) }
}
.subscribeBy( onError = { }, onComplete = { }, onNext = { })
}
I'm just trying to work out the code before I get access to the hardware so can't test this at the moment and am wondering if it's feasible. I'm more worried about the read portion if the complete response might be not be within one readCharacteristic(). If not would having a long running read pumping into a buffer that can be handled one byte at a time and removing only those bytes that make up a valid response and reading from that instead.
Thoughts? This seems like a common usage pattern, but I've been unable to find anything like it as far as samples.
Related
I am building an application in which user can read BPM data in mobile. I was reading in some posts that I need to build a queue for that, it run at a time and hold next command in queue until it finish the first job. I used some piece of code from the library. I want to check my existing queue why is it slow? if anything which is more efficient then ConcurrentLinkedQueue, then definitely I'll try that. I was reading some articles with Channel is type of Queue which behaves First In First Out. TBH I don't know it will work. Can you guys help me on this?
This is function of setupQueuePolling
private fun setupQueuePolling() {
viewModelScope.launch(Dispatchers.IO) {
Log.e(TAG, "Starting Polling")
while (true) {
synchronized(commandQueue) {
if (!commandQueue.isEmpty()) {
commandQueue.poll()?.let { qItem ->
qItem("This is input")
}
}
}
}
}
}
I have added the queue command for calling this function addItemToQueue
fun addItemToQueue(item: (input: String) -> Unit) {
Log.e(TAG, "Added Item ->> $item")
commandQueue.add(item)
}
I am calling addItemToQueue in MainActivity.kt, onConnectionStateChange, onServicesDiscovered & onCharacteristicChanged with GlobalScope using onServicesDiscovered & startScan.
I don't understand, why my queue is so slow to response back in time. Library is very fast to give response. My whole project is in here.
Thanks
At first glance, it's quite hard to say why it is slow. What I see is that synchronized(commandQueue) is being used while ConcurrentLinkedQueue is already a thread-safe queue, so the synchronized(commandQueue) can be omitted.
Using features of Kotlin coroutines, I would use a Flow in this case, particularly MutableSharedFlow. It is thread-safe and uses principles of queue. For example in this case it would look like the following:
private val commandFlow = MutableSharedFlow<(input: String) -> Unit>()
suspend fun addItemToQueue(item: (input: String) -> Unit) {
commandFlow.emit(item1) // emitting item to commandFlow
}
private fun setupQueuePolling() {
viewModelScope.launch {
// handle commands emitted in addItemToQueue() method.
commandFlow.collect { item ->
item("This is input")
}
}
}
If this doesn't improve the speed, further investigation should be made, perhaps BLE device executes commands slowly. Additional logs of each operation could be helpful.
Im currently developing an android app with Kotlin. Its my first experience with this programming language and im currently struggeling to translate an example of Java code to Kotlin.
I want to implement the answer to this question in Kotlin.
my current implementation fails to compile because the Ovservable::from method seems to be removed.
This is my current approach:
connectionObservable!!.flatMap { connection ->
connection.discoverServices()
.flatMap { services ->
services.getService(UUID.fromString("My UUID")).map(BluetoothGattService::getCharacteristics)
//here occurs the error, he wants a Single Source but got a observable with the ble characteristic
.flatMap { characteristics: MutableList<BluetoothGattCharacteristic> -> Observable.fromIterable(characteristics) }
.flatMap { characteristic: BluetoothGattCharacteristic ->
connection.setupNotification(characteristic)
.flatMap { observable: Observable<ByteArray> -> observable ,Pair<BluetoothGattCharacteristic, ByteArray>(characteristic, observable)}
}
}
}.subscribe { pair: Pair<BluetoothGattCharacteristic, ByteArray> ->
genericModel[pair.first.uuid] = pair.second
throwable -> { /* handle errors */ }
}
Can you point out my errors so i can understand what im doing wrong?
Thanks in advance!
Jonas
There are several potential issues with your code.
First of all the code is not syntactically correct — see this line:
.flatMap { observable: Observable<ByteArray> -> observable ,Pair<BluetoothGattCharacteristic, ByteArray>(characteristic, observable)}
I assume you probably wanted to flatMap the observable: Observable<ByteArray> (type added by me for clarity) to get from it ByteArray objects. This would look like this:
.flatMap { observable: Observable<ByteArray> -> observable.map { bytes -> Pair(characteristic, bytes) }}
Additionally the code will not compile as you try to return an Observable from a lambda which expects a SingleSource — exactly as your compiler says. If you have a Single and you will .flatMap it — it's lambda is supposed to return another SingleSource. There is a .flatMapObservable function that expects ObservableSource which is what you should use. The end result would look like:
connectionObservable!!.flatMap { connection ->
connection.discoverServices()
.flatMapObservable { services ->
services.getService(UUID.fromString("My UUID")).map(BluetoothGattService::getCharacteristics)
//here occurs the error, he wants a Single Source but got a observable with the ble characteristic
.flatMapObservable { characteristics: MutableList<BluetoothGattCharacteristic> -> Observable.fromIterable(characteristics) }
.flatMap { characteristic: BluetoothGattCharacteristic ->
connection.setupNotification(characteristic)
.flatMap { observable: Observable<ByteArray> -> observable.map { bytes -> Pair(characteristic, bytes) }}
}
}
}.subscribe { pair: Pair<BluetoothGattCharacteristic, ByteArray> ->
genericModel[pair.first.uuid] = pair.second
throwable -> { /* handle errors */ }
}
Observable.fromIterable() is still the API of Observable. You probably do not use the Observable class from proper package. There is an Observable class in package java.util but we are using here one from RxJava 2 which has package io.reactivex
I has very simple task to send request for remote data. But need to handle my request and get local on remote error.
Functionality way like a mathematics has many ways to solve problem.
So first ost simple сomes to mind to do this:
val remote = remotePurchase
.getAvailableUsersMaxCount()
.subscribe({},{
//on failed
getLocal()
})
But i thing it is rx beginner guy also it is callback in callback bad way.
Also i think to do this:
override fun getActualUsersMaxCount(): Observable<Int> {
return remotePurchase
.getAvailableUsersMaxCount()
.flatMap {
updateUsersCount(it)
}
.onErrorReturn {
userLocal.getById(PrefProvider.userId).map {
it.chatUsersCount?:0
}.blockingFirst()
}
}
Bute some times handle:
Caused by: rx.exceptions.CompositeException: 2 exceptions occurred.
Like this github.com/ReactiveX/RxJava/issues/3679
Then i known about using toBlocking is not best idea. See post here.
After some time i decided mutch better this:
override fun getActualUsersMaxCount(): Observable<Int> {
val remote = remotePurchase
.getAvailableUsersMaxCount()
.flatMap {
updateUsersCount(it)
}.onErrorReturn { -1 }
return zip(remote, userLocal.getById(PrefProvider.userId).map {
it.chatUsersCount ?: 0
}, { remoteCount, localCount ->
if(remoteCount!= -1) remoteCount else localCount
})
}
So now i almost control situation. If i got error from network I can check it and receive local result
But what variation better as you think? May be there is more simplify decision?
You can use onErrorResumeNext to avoid the blockingFirst call in your second solution. This makes it the better solution, in my opinion. Not sure what two exceptions, you are talking about. Can give some more context on what they are, as I don't see the issue.
override fun getActualUsersMaxCount(): Observable<Int> {
return remotePurchase
.getAvailableUsersMaxCount()
.flatMap {
updateUsersCount(it)
}
.onErrorResumeNext(
userLocal.getById(PrefProvider.userId).map {
it.chatUsersCount?:0
})
}
This is my code which does some background work
fun getAllArtists(): LiveData<List<Artist>> {
val artistListLiveData = MutableLiveData<List<Artist>>()
doAsync {
val artistList = MusicGenerator.getAllArtists()
onComplete {
getArtistInfo(artistList)
artistListLiveData.value = artistList
}
}
return artistListLiveData
}
On completion I make a network call to get Artist Info
private fun getArtistInfo(artistList: List<Artist>) {
artistList.forEach {
val url = "http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&api_key=API_KEY&format=json"
.plus("&artist=")
.plus(it.artistName)
val artistInfoList: MutableList<ArtistInfo> = ArrayList()
apiService.getArtistImage(url)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({ result ->
info { result.toString() }
}, { error ->
error.printStackTrace()
})
verbose { artistInfoList.size }
}
}
However, I'm making sure that the network call is in the background thread and results are on the main thread.
But there is jank in the UI, and the logcat says too much work being done on the main thread. I don't get it, what am I doing wrong here?
I suspect you are creating too many threads. io() is unbounded, and computation is based on the processor cores. Since you are doing io, you should be using io(), but also need to take care to not blast a ton of requests at the same time. You can use a Flowable.flatMap to iterate through your list instead of foreach. The key here is to specify a value for the max concurrency to flatMap. Below, I have set it to 4, but you can play around with the number to see what gives you a good result for max requests inflight without creating jank. Also, since we are using flatMap, I moved your subscribe outside the loop to process the stream of results coming from getArtistImage. It is not clear what you are doing with artistInfoList from your code snippet, so I have left it off, but you can use the following as a guide --
private fun getArtistInfo(artistList: List<Artist>) {
Flowable.fromIterable(artistList).flatMap({
val url = "http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&api_key=API_KEY&format=json"
.plus("&artist=")
.plus(it.artistName)
getArtistImage(url)
.subscribeOn(Schedulers.io())
}, 4)
.subscribe({ result ->
info { result.toString() }
}, { error ->
error.printStackTrace()
})
}
i'm trying to learn chain requests with RxJava + Retrofit + Kotlin.
The tutorial i was follwing was using RxJava 1.x, so when i try to re-implement with RxJava 2.x the i cannot get .zip() to work.
It's a simple app using Star Wars API, returning a list of movies, ando for each movie, get the characters from them.
fun loadMoviesFull(): Observable<Movie> {
return service.listMovies()
.flatMap { filmResults -> Observable.from(filmResults.results) }
.flatMap { film ->
Observable.zip(
Observable.just(Movie(film.title, film.episodeId, ArrayList<Character>())),
Observable.from(film.personUrls)
.flatMap { personUrl ->
service.loadPerson(Uri.parse(personUrl).lastPathSegment)
}
.map { person ->
Character(person!!.name, person.gender)
}
.toList(),
{ movie, characters ->
movie.characters.addAll(characters)
movie
})
}
}
If you want to see the whole implementation of the tutorial, this is the link(The comments are in portuguese):
http://www.nglauber.com.br/2017/03/rxjava-kotlin-retrofit-star-wars-api.html
I just want to know the syntax for ir, because i cannot implement on 2.x.
Thank you so much and i'm sorry for my bad english.
I don't know what error compiler produces but very probably your function should return Observable<List<Movie>>, zip's BiFunction requires passing types explicitly and should not have single movie at the end. And of course .toList() at the end.
Full code:
fun loadMoviesFull(): Observable<List<Movie>> {
return service.listMovies()
.flatMap { filmResults -> Observable.from(filmResults.results) }
.flatMap { film ->
Observable.zip(
Observable.just(Movie(film.title, film.episodeId, ArrayList<Character>())),
Observable.from(film.personUrls)
.flatMap { personUrl ->
service.loadPerson(Uri.parse(personUrl).lastPathSegment)
}
.map { person ->
Character(person!!.name, person.gender)
}
.toList(),
BiFunction<Movie, Character, Movie>{ movie, characters ->
movie.characters.addAll(characters)
})
}.toList()
}
It won't let me add a comment due to insufficient reputation, hence posting as an answer. Are you using rxkotlin? There are a number of helper functions, including Observable.zip() that help with the SAM ambiguity issues when using Rx2. Without knowing what your error is, that's the best advice I can give.