2

The Wear OS tiles example is great, not so much of an issue but how would one start the background media service that play the songs selected in the primary app, when every I try to start the service, I get the following error. The is no UI thread to reference and the documentation only has to methods for onclick, LoadAction and LaunchAction.

override fun onTileRequest(request: TileRequest) = serviceScope.future {
when(request.state!!.lastClickableId){
"play"-> playClicked()
}....

suspend fun playClicked(){

    try {
        // Convert the asynchronous callback to a suspending coroutine
        suspendCancellableCoroutine<Unit> { cont ->
            mMediaBrowserCompat = MediaBrowserCompat(
                applicationContext, ComponentName(applicationContext, MusicService::class.java),
                mMediaBrowserCompatConnectionCallback, null
            )
            mMediaBrowserCompat!!.connect()

        }
    }catch (e:Exception){
        e.printStackTrace()
    } finally {
      mMediaBrowserCompat!!.disconnect()
    }
}

ERROR

java.lang.RuntimeException: Can't create handler inside thread Thread[DefaultDispatcher-worker-1,5,main] that has not called Looper.prepare()
TofferJ
  • 4,678
  • 1
  • 37
  • 49
Vicky P
  • 63
  • 6

4 Answers4

2

serviceScope is running on Dispatchers.IO, you should use withContext(Dispatchers.Main) when making any calls to MediaBrowserCompat.

Yuri Schimke
  • 12,435
  • 3
  • 35
  • 69
1

Thanks Yuri that worked but, it ended up blocking the UI thread, the solution that is work is below

fun playClicked(){
    mainHandler.post(playSong)
}

  private val playSong: Runnable = object : Runnable {
        @RequiresApi(Build.VERSION_CODES.N)
        override fun run() {
            mMediaBrowserCompat = MediaBrowserCompat(
                applicationContext, ComponentName(applicationContext, MusicaWearService::class.java),
                mMediaBrowserCompatConnectionCallback, null
            )
            mMediaBrowserCompat!!.connect()
        }
    }
Boken
  • 4,825
  • 10
  • 32
  • 42
Vicky P
  • 63
  • 6
1

Responding to the answer above, the serviceScope.future creates a CoroutineScope that will cause the future returned to the service to wait for all child jobs to complete.

If you want to have it run detached from the onTileRequest call, you can run the following, which will launch a new job inside the application GlobalScope and let the onTileRequest return immediately.

"play" -> GlobalScope.launch { 

}

The benefit to this is that you don't throw a third concurrency model into the mix, ListenableFutures, Coroutines, and now Handler. LF and Coroutines are meant to avoid you having to resort to a third concurrency option.

Boken
  • 4,825
  • 10
  • 32
  • 42
Yuri Schimke
  • 12,435
  • 3
  • 35
  • 69
0

Cool Yuri, the below worked and I think is more efficient

fun playClicked() = GlobalScope.launch(Dispatchers.Main) {
            mMediaBrowserCompat = MediaBrowserCompat(
                applicationContext, ComponentName(applicationContext, MusicaWearService::class.java),
                mMediaBrowserCompatConnectionCallback, null
            )
            mMediaBrowserCompat!!.connect()
        }
Vicky P
  • 63
  • 6