Hi, I'm trying to setup a channel producer like ex...
# android
l
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`:
Copy code
@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:
Copy code
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
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
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
Are you trying to reuse code from there? https://github.com/Beepiz/BleScanCoroutines
l
yes but you are using coroutines with experimental state so I couldn't use it with a newer Kotlin version
l
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
@Lilly Yes, you can start your producer coroutine with the following :
Copy code
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
@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
@Lilly I just released version 0.2.0 of BleScanCoroutines, with a simpler API, and it uses only one channel!
😎 1
l
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:
Copy code
@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
@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
@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.
Copy code
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
@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
@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
Yes, if you don't need a channel and don't need it in the coroutine world
l
Alright, thanks for your investigation. Btw I will use your BleGattCoroutine library. Keep going with the great work 🙂
l
I'm glad it's helpful 🙂