Android ML-kit chaing together multiple tasks - android

I'm trying to run multiple processes using the ml-kit, I've already searched up and the only solutions I've found was to either do all the tasks in succession (how?) or using RXJava and the zip utility but that doesn't seem to match what I need.
I've tried writing the following code but i'm unsure to how good it is, would this be a good way of doing it?
override fun analyze(image: ImageProxy) {
val inputImage = InputImage.fromMediaImage(image.image!!, image.imageInfo.rotationDegrees)
val tasks = mutableListOf<Task<*>>()
val onComplete = { t: Task<*> ->
tasks.remove(t)
if (tasks.isEmpty()) {
image.close()
}
}
barcodeScanner?.process(inputImage)
?.addOnSuccessListener {
// Do stuff with the result
}
?.addOnCompleteListener(onComplete)
?.also {
tasks.add(it)
}
imageLabeler?.process(inputImage)
?.addOnSuccessListener {
// Do stuff with the result
}
?.addOnCompleteListener(onComplete)
?.also {
tasks.add(it)
}
faceDetector?.process(inputImage)
?.addOnSuccessListener {
// Do stuff with the result
}
?.addOnCompleteListener(onComplete)
?.also {
tasks.add(it)
}
}

You current way will make different inference task queue up in one background thread. If you want multi-threading, you can use XxxOptions.Builder #setExecutor to assign detectors different thread.
https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/BarcodeScannerOptions.Builder#setExecutor(java.util.concurrent.Executor)

Related

Kotlin Flow not collected anymore after working initially

Basically I want to make a network request when initiated by the user, collect the Flow returned by the repository and run some code depending on the result. My current setup looks like this:
Viewmodel
private val _requestResult = MutableSharedFlow<Result<Data>>()
val requestResult = _requestResult.filterNotNull().shareIn(
scope = viewModelScope,
started = SharingStarted.WhileViewSubscribed,
replay = 0
)
fun makeRequest() {
viewModelScope.launch {
repository.makeRequest().collect { _requestResult.emit(it) }
}
}
Fragment
buttonLayout.listener = object : BottomButtonLayout.Listener {
override fun onButtonClick() {
viewModel.makeRequest()
}
}
lifecycleScope.launchWhenCreated {
viewModel.requestResult.collect { result ->
when (result) {
Result.Loading -> {
doStuff()
}
is Result.Success -> {
doDifferentStuff(result.data)
}
is Result.Failure -> {
handleError()
}
}
}
}
The first time the request is made everything seems to work. But starting with the second time the collect block in the fragment does not run anymore. The request is still made, the repository returns the flow as expected, the collect block in the viewmodel runs and emit() also seems to be executed successfully.
So what could be the problem here? Something about the coroutine scopes? Admittedly I lack any sort of deeper understanding of the matter at hand.
Also is there a more efficient way of accomplishing what I'm attempting using Kotlin Flows in general? Collecting a flow and then emitting the same flow again seems a bit counterintuitive.
Thanks in advance:)
According to the documentation there are two recommended alternatives:
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
//your thing
}
}
I rather the other alternative:
viewLifecycleOwner.lifecycleScope.launch {
viewModel.makeReques().flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
.collect {
// Process the value.
}
}
I like the flowWithLifecycle shorter syntax and less boiler plate. Be carefull thar is bloking so you cant have anything after that.
The oficial docs
https://developer.android.com/topic/libraries/architecture/coroutines
Please be aware you need the lifecycle aware library.

Firebase ML-Kit run multiple face detection sessions in parallel

As part of my ML project, I want to generate training data for analyzing the face of multiple individuals from different images using the face detector Google Firebase ML-Kit Face detection library. I created a very simple service class to encapsulate the initialization and start the process of face detection:
class FaceDetectorService(private val act: MainActivity) {
private var opts: FirebaseVisionFaceDetectorOptions? = null
private var detector: FirebaseVisionFaceDetector? = null
init {
FirebaseApp.initializeApp(act)
opts = FirebaseVisionFaceDetectorOptions.Builder()
.setPerformanceMode(FirebaseVisionFaceDetectorOptions.ACCURATE)
.setLandmarkMode(FirebaseVisionFaceDetectorOptions.NO_LANDMARKS)
.setClassificationMode(FirebaseVisionFaceDetectorOptions.NO_CLASSIFICATIONS)
.setContourMode(FirebaseVisionFaceDetectorOptions.ALL_CONTOURS)
.build()
detector = FirebaseVision.getInstance()
.getVisionFaceDetector(opts!!)
}
suspend fun analyzeAsync(cont: Context, uri: Uri) : Pair<String, Task<List<FirebaseVisionFace>>> {
val image = FirebaseVisionImage.fromFilePath(cont, uri)
// this is for the UI thread
withContext(Main){
act.addItemToAnalyze(uri.lastPathSegment)
}
// return the filename too
return Pair(uri.lastPathSegment, detector!!.detectInImage(image))
}
}
The function detector!!.detectInImage (FirebaseVisionImage.detectInImage ) returns a Task that represents async operations.
In the onResume() function of my MainActivity, inside a CoroutineScope, I fire up the lib and start iterating over the images converting them to an Uri first then passing it to the face detector:
CoroutineScope(IO).launch {
val executeTime = measureTimeMillis {
for (uri in uris){
val fileNameUnderAnalysis = uri.lastPathSegment
//val tsk = withContext(IO) {
// detector!!.analyzeAsync(act, uri)
//}
val tsk = detector!!.analyzeAsync(act, uri)
tsk.second.addOnCompleteListener { task ->
if (task.isSuccessful && task.result!!.isNotEmpty()) {
try {
// my best
} catch (e: IllegalArgumentException) {
// fire
}
} else if (task.result!!.isEmpty()) {
// not today :(
}
}
tsk.second.addOnFailureListener { e ->
// on error
}
}
}
Log.i("MILLIS", executeTime.toString())
}
Now, although my implementation runs concurrently (that is, starting at the same time), what I actually want is to run them in parallel (running in the same time depending on the number of threads, which is 4 in my case on an emulator), so my goal would be to take the number of available threads and assign an analysis operation to each of them quartering the execution time.
What I tried so far is, inside the CoroutineScope(IO).launch block, encapsulating the call to the library in a task:
val tsk = async {
detector!!.analyzeAsync(act, uri)
}
val result = tsk.await()
and a job:
val tsk = withContext(IO) {
detector!!.analyzeAsync(act, uri)
}
but the async operations I manually start always last only as long as the Firebase tasks are started, not waiting for the inner task to run to completion. I also tried adding different withcontext(...) and ...launch {} variations inside the class FaceDetectorService, but to no avail.
I'm obviously very new to kotlin coroutines, so I think I'm missing something very basic here, but I just cannot wrap my head around it.
(PS: please do not comment on the sloppiness of the code, this is just a prototype :) )
analyzeAsync() is a suspend fun and also returns a future-like Task object. Instead it should return the result of Task.await(), which you can easily implement basically by factoring out your addOnCompleteListener call:
suspend fun <T> Task<T>.await(): T = suspendCancellableCoroutine { cont ->
addOnCompleteListener {
val e = exception
when {
e != null -> cont.resumeWithException(e)
isCanceled -> cont.cancel()
else -> cont.resume(result)
}
}
}
An optimzied version is available in the kotlinx-coroutines-play-services module).
Since the face detection API is already async, it means the thread you call it on is irrelevant and it handles its computation resources internally. Therefore you don't need to launch in the IO dispatcher, you can use Main and freely do GUI work at any point, with no context switches.
As for your main point: I couldn't find explicit details on it, but it is highly likely that a single face detection call already uses all the available CPU or even dedicated ML circuits that are now appearing in smartphones, which mean there's nothing to parallelize from the outside. Just a single face detection request is already getting all the resources working on it.

Parallel request with Retrofit, Coroutines and Suspend functions

I'm using Retrofit in order to make some network requests. I'm also using the Coroutines in combination with 'suspend' functions.
My question is: Is there a way to improve the following code. The idea is to launch multiple requests in parallels and wait for them all to finish before continuing the function.
lifecycleScope.launch {
try {
itemIds.forEach { itemId ->
withContext(Dispatchers.IO) { itemById[itemId] = MyService.getItem(itemId) }
}
} catch (exception: Exception) {
exception.printStackTrace()
}
Log.i(TAG, "All requests have been executed")
}
(Note that "MyService.getItem()" is a 'suspend' function.)
I guess that there is something nicer than a foreach in this case.
Anyone with an idea?
I've prepared three approaches to solving this, from the simplest to the most correct one. To simplify the presentation of the approaches, I have extracted this common code:
lifecycleScope.launch {
val itemById = try {
fetchItems(itemIds)
} catch (exception: Exception) {
exception.printStackTrace()
}
Log.i(TAG, "Fetched these items: $itemById")
}
Before I go on, a general note: your getItem() function is suspendable, you have no need to submit it to the IO dispatcher. All your coroutines can run on the main thread.
Now let's see how we can implement fetchItems(itemIds).
1. Simple forEach
Here we take advantage of the fact that all the coroutine code can run on the main thread:
suspend fun fetchItems(itemIds: Iterable<Long>): Map<Long, Item> {
val itemById = mutableMapOf<Long, Item>()
coroutineScope {
itemIds.forEach { itemId ->
launch { itemById[itemId] = MyService.getItem(itemId) }
}
}
return itemById
}
coroutineScope will wait for all the coroutines you launch inside it. Even though they all run concurrently to each other, the launched coroutines still dispatch to the single (main) thread, so there is no concurrency issue with updating the map from each of them.
2. Thread-Safe Variant
The fact that it leverages the properties of a single-threaded context can be seen as a limitation of the first approach: it doesn't generalize to threadpool-based contexts. We can avoid this limitation by relying on the async-await mechanism:
suspend fun fetchItems(itemIds: Iterable<Long>): Map<Long, Item> = coroutineScope {
itemIds.map { itemId -> async { itemId to MyService.getItem(itemId) } }
.map { it.await() }
.toMap()
}
Here we rely on two non-obvious properties of Collection.map():
It performs all the transformation eagerly, so the first transformation to a collection of Deferred<Pair<Long, Item>> is completely done before entering the second stage, where we await on all of them.
It is an inline function, which allows us to write suspendable code in it even though the function itself is not a suspend fun and gets a non-suspendable lambda (Deferred<T>) -> T.
This means that all the fetching is done concurrently, but the map gets assembled in a single coroutine.
3. Flow-Based Approach with Improved Concurrency Control
The above solved the concurrency for us, but it lacks any backpressure. If your input list is very large, you'll want to put a limit on how many simultaneous network requests you're making.
You can do this with a Flow-based idiom:
suspend fun fetchItems(itemIds: Iterable<Long>): Map<Long, Item> = itemIds
.asFlow()
.flatMapMerge(concurrency = MAX_CONCURRENT_REQUESTS) { itemId ->
flow { emit(itemId to MyService.getItem(itemId)) }
}
.toMap()
Here the magic is in the .flatMapMerge operation. You give it a function (T) -> Flow<R> and it will execute it sequentially on all the input, but then it will concurrently collect all the flows it got. Note that I couldn't simplify flow { emit(getItem()) } } to just flowOf(getItem()) because getItem() must be called lazily, while collecting the flow.
Flow.toMap() is not currently provided in the standard library, so here it is:
suspend fun <K, V> Flow<Pair<K, V>>.toMap(): Map<K, V> {
val result = mutableMapOf<K, V>()
collect { (k, v) -> result[k] = v }
return result
}
If you are looking for just a nicer way to write it and eliminate foreach
lifecycleScope.launch {
try {
itemIds.asFlow()
.flowOn(Dispatchers.IO)
.collect{ itemId -> itemById[itemId] = MyService.getItem(itemId)}
} catch (exception: Exception) {
exception.printStackTrace()
}
Log.i(TAG, "All requests have been executed")
}
Also please look at lifecycleScope I suspect it is using Dispatchers.Main. If that is the case you can remove this .flowOn(Dispatchers.IO) extra dispatcher declaration.
For more info: Kotlin Asynchronous Flow

Kotlin coroutines - start another task if after some time the first one doesn't finish

I am using Kotlin coroutines to get data from the server, I am passing the deferred over to other functions. In case the server doesn't give an answer in 2000 ms I would like to retrive the object from a local Room DB (if it exists in a local database), but if I finally receive data from the server I would like to save in in a local DB for future calls. How can I acheive that? I thought about using withTimeout, but in this situation, there is no waiting for a response from the server after timeout.
override fun getDocument(): Deferred<Document> {
return GlobalScope.async {
withTimeoutOrNull(timeOut) {
serverC.getDocument().await()
} ?: dbC.getDocument().await()
}
}
An idea I came up with:
fun getDocuments(): Deferred<Array<Document>> {
return GlobalScope.async {
val s = serverC.getDocuments()
delay(2000)
if (!s.isCompleted) {
GlobalScope.launch {
dbC.addDocuments(s.await())
}
val fromDb = dbC.getDocuments().await()
if (fromDb != null) {
fromDb
} else {
s.await()
}
} else {
s.await()
}
}
}
I recommend using the select expression from the kotlinx.coroutines library.
https://kotlinlang.org/docs/reference/coroutines/select-expression.html
fun CoroutineScope.getDocumentsRemote(): Deferred<List<Document>>
fun CoroutineScope.getDocumentsLocal(): Deferred<List<Document>>
#UseExperimental(ExperimentalCoroutinesApi::class)
fun CoroutineScope.getDocuments(): Deferred<List<Document>> = async {
supervisorScope {
val documents = getDocumentsRemote()
select<List<Document>> {
onTimeout(100) {
documents.cancel()
getDocumentsLocal().await()
}
documents.onAwait {
it
}
}
}
}
The select expression resumes either with the onAwait signal from the network or with the timeout. We return the local data in that case.
You may want to load documents in chunks as well, for that Channels may help too
https://kotlinlang.org/docs/reference/coroutines/channels.html
And lastly, we use an Experimental API of kotlinx.coroutines in the example, the function onTimeout may change in the future versions of the library

Convert Listener to Single in RxJava2

I am using the Play Services Auth api Phone and so far I have the foll
fun startSmsListener() {
val client = SmsRetriever.getClient(applicationContext /* context */);
val task = client.startSmsRetriever();
task.addOnSuccessListener(object : OnSuccessListener<Void> {
override fun onSuccess(p0: Void?) {
//do somethin
}
})
task.addOnFailureListener(object : OnFailureListener {
override fun onFailure(p0: Exception) {
//Handle error
}
})
}
Now I want to put this in an SmsManager class and convert it into an Single/Observable so I can handle it in a reactive way in my viewmodel. How can I do that?
So far I've got this:
var single = Single.create(SingleOnSubscribe<Void> { e ->
val task = client.startSmsRetriever()
task.addOnSuccessListener {
e.onSuccess(it)
}
task.addOnFailureListener {
e.onError(it)
}
})
But I am unsure as to whether this code is correct or not, whether there is something im missing like removing the listeners after disposal.
Any help?
You are interested in a "boolean" value - either connected or not connected, thus instead of Single you should use Completable:
Completable.create { emitter ->
val client = SmsRetriever.getClient(applicationContext)
val task = client.startSmsRetriever()
task.addOnSuccessListener { emitter.onComplete() }
task.addOnFailureListener { emitter.tryOnError(it) }
}
While creating a Completable manually will work, you might also have a look at the RxTask project. It provides "RxJava 2 binding for Google Play Services Task APIs".
If you need it just in one place, an extra library would certainly be an overkill. But if you plan to use more Play Services together with RxJava, it might be worth a look...
It doesn't (yet) provide a wrapper explicitly for SmsRetriever, but the general task helper classes would probably be enough:
val client = SmsRetriever.getClient(applicationContext)
val smsReceiver = CompletableTask.create(client::startSmsRetriever)

Categories

Resources