https://kotlinlang.org logo
Title
l

Lilly

03/27/2019, 5:53 PM
Hi, I'm trying to setup a channel producer like explained here (https://kotlinlang.org/docs/reference/coroutines/channels.html) - keyword: coroutines - but I don't know how I have to use a scope so that the
produce
method is available within `scan`:
@ObsoleteCoroutinesApi
    @ExperimentalCoroutinesApi
    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
    fun scan(filters: List<ScanFilter>?, settings: ScanSettings): ReceiveChannel<ScanResult> {
        requireSafeSettings(settings)
        return produce(context) { // unresolvable reference
            val scanResultsChannel = Channel<ScanResult>(capacity = UNLIMITED)
            val scanCallback = ScanCallback(scanResultsChannel)
            scanner.startScan(filters, settings, scanCallback)
            scanResultsChannel.consumeEach { send(it) }
            scanResultsChannel.invokeOnClose { scanner.stopScan(scanCallback) }

            if (SDK_INT >= O_MR1 && scanCallback.isScanningTooFrequently()) {
                scanResultsChannel.close(ScanFailedException(SCAN_FAILED_SCANNING_TOO_FREQUENTLY))
            }
        }
    }
I call this method within
ScannerLiveData
class that extends from LiveData:
fun startScan() {
        if (isScanning) {
            return
        }

        scanChannel = scanner.scan(null, settings)

        scanChannel.consumeEach {  // unresolvable reference
            // Todo
        }

        isScanning = true
    }
My
ScannerLiveData
object gets injected in my
ScannerViewModel
where I simply call
startScan()
. I don't know how to provide a scope to use the
scan
method. I know there is a predefined
viewModelScope
...The only idea I have is to pass this scope down to
scan
method. Can someone help?
t

tseisel

03/27/2019, 6:06 PM
You have to think in term of lifecycle : should
ScannerLiveData
instances live longer that its
ViewModel
? If no, create instances of
ScannerLiveData
by passing the scope of the viewmodel. Otherwise, you have to define your own scope for your LiveData and cancel it when it is not longer required.
l

Lilly

03/27/2019, 6:26 PM
ScannerLiveData
doesn't live longer so I would pass the viewmodel scope to
ScannerLiveData
via constructor but will I have to pass the scope to
scan
method too?
l

louiscad

03/27/2019, 6:31 PM
Are you trying to reuse code from there? https://github.com/Beepiz/BleScanCoroutines
l

Lilly

03/27/2019, 6:33 PM
yes but you are using coroutines with experimental state so I couldn't use it with a newer Kotlin version
l

louiscad

03/27/2019, 6:36 PM
Yes, I didn't update it yet because I wasn't so happy with the API. I'll try to take time to make an update this night, I use a modified version successfully in a private project, it's good enough.
You can subscribe to this issue to be notified when I updated the project: https://github.com/Beepiz/BleScanCoroutines/issues/3
t

tseisel

03/27/2019, 6:44 PM
@Lilly Yes, you can start your producer coroutine with the following :
return scope.produce { ... }
Note that you need to pass a parameter to
produce
only if you want the producer coroutine to have some context (for example, move away from the main thread by passing
<http://Dispatchers.IO|Dispatchers.IO>
)
l

Lilly

03/27/2019, 7:19 PM
@louiscad Alright Sir. Btw...I'm not soo familiar with coroutines yet like you but is it neccessary to create the extra
scanResultsChannel
. Isn't there a possibility to just have one channel, i.e. create a channel outside of your library and pass it to
scan
and then to
ScanCallback
? @tseisel Thanks Sir and ty for the interesting bonus information 🙂
l

louiscad

03/27/2019, 11:33 PM
@Lilly I just released version 0.2.0 of BleScanCoroutines, with a simpler API, and it uses only one channel!
😎 1
l

Lilly

03/27/2019, 11:50 PM
haha nice man! I will have a look tomorrow 🙂
@louiscad From my viewModel I call the
startScan
method of my
ScannerLiveData
object which looks like:
@ExperimentalCoroutinesApi
    @ObsoleteCoroutinesApi
    fun startScan(viewModelScope: CoroutineScope) {
        if (isScanning) {
            return
        }

        scanChannel = scanner.scanChannel(null, settings).apply {
            viewModelScope.launch { consumeEach { deviceDiscovered(it) } }
        }

        isScanning = true
    }
As you can see I pass the
viewModelScope
--> should I call
cancel
for this scope in
onCleared
method of viewModel or in
stopScan
method in viewModel? Thanks in advance!
l

louiscad

04/04/2019, 10:17 AM
@Lilly I'm not sure why you use coroutines here if it's to just end up with a pair of start and stop functions. My library is made so you scan, consuming the channel, taking advantage of being in a coroutine, and when you stop the channel, it is automatically closed. The scope of the consumption should be whatever best fit your needs, which would be one from ViewModel (cancelled in onCleared, which is automatically fone for you if you use the recently released viewmodel-ktx), or a child of it `from a
coroutineScope { … }
, or a coroutine builder. Note that
Flow
(aka. cold streams from kotlinx.coroutines) is coming and should be a better fit for that API, and when the API is stable enough, I'll likely update BleScanCoroutines for it.
l

Lilly

04/04/2019, 11:18 AM
@louiscad Hmm I can't consume the channel without a coroutine. When I try to iterate over the channel I get the message to use it within a coroutine.
scanChannel = scanner.scanChannel(null, settings)
        for (result in scanChannel) deviceDiscovered(result)
Btw I'm using the
viewModelScope
provided by
viewmodel-ktx
, just wasn't sure if it gets canceled automatically, but you're right.
l

louiscad

04/04/2019, 11:27 AM
@Lilly I was talking about using the raw API, without my library, when mentioning start/stop functions pair
Beware, the for loop doesn't close the channel automatically like consumeEach does
l

Lilly

04/04/2019, 11:43 AM
@louiscad Sorry I can't follow you...do you suggest not to use your library in my case?
Ah ok, I guess I understand...let me think about it
l

louiscad

04/04/2019, 12:13 PM
Yes, if you don't need a channel and don't need it in the coroutine world
l

Lilly

04/04/2019, 12:16 PM
Alright, thanks for your investigation. Btw I will use your BleGattCoroutine library. Keep going with the great work 🙂
l

louiscad

04/04/2019, 12:52 PM
I'm glad it's helpful 🙂