Home > OS >  How to work properly with coroutines in delegated class?
How to work properly with coroutines in delegated class?

Time:11-05

I would need advice on how to properly pass a coroutine context or scope to a deledate class. I have this base:

interface MyDelegate {
    fun doWork()
}

class MyDelegateImpl() : MyDelegate {
    override fun doWork() {
        var job = async(Dispatcher.Default) {
            // .....
        }
        .....
    }
}

class MainViewModel(application: Application): AndroidViewModel(application), CoroutineScope, MyDelegate by MyDelegateImpl() {
    .....
}

class MainActivity : AppCompatActivity() {
    .....
    private lateinit var mainViewModel: MainViewModel
    
    .....
    
    private fun initialize() {
        .....
        mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
    }
    
    fun doAction() {
        mainViewModel.doWork()
    }
}

When building, it throws an error for async in doWork():

Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: public fun CoroutineScope.async(context: CoroutineContext = ..., start: CoroutineStart = ..., block: suspend CoroutineScope.() -> TypeVariable(T)): Deferred<TypeVariable(T)> defined in kotlinx.coroutines

I tried to extend MyDelegateImpl with the CouroutineScope interface:

class MyDelegateImpl(override val coroutineContext: CoroutineContext) : MyDelegate, CoroutineScope {
    .....
}

but then I don't know how I should pass it in MainViewModel, of course it's not possible in the list of interfaces.

CodePudding user response:

First, I'd give your worker class a CoroutineScope property in its constructor for it to use:

class MyDelegateImpl(val scope: CoroutineScope) : MyDelegate {
    override fun doWork() {
        var job = scope.async(Dispatcher.Default) {
            // .....
        }
        .....
    }
}

The next part is kind of awkward. You would usually want to use the provided viewModelScope since it's already available and set up to cancel itself appropriately. But it's created lazily, so it's not available during class construction time such that you could pass it to a delegate.

I think in this case, I would shadow the viewModelScope extension property so you aren't creating a redundant scope. You need to clean it up in onCleared().

It needs to be in the primary constructor so it can be passed to the delegate, but you want to encapsulate its creation, so I'd make the primary constructor private and create it in a public secondary constructor.

class MainViewModel private constructor(
    val viewModelScope: CoroutineScope,
    application: Application
): AndroidViewModel(application), MyDelegate by MyDelegateImpl(viewModelScope) {
    
    constructor(application: Application): this(
        CoroutineScope(SupervisorJob()   Dispatchers.Main.immediate),
        application
    )

    override fun onCleared() {
        viewModelScope.cancel()
    }
}
  • Related