Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clean way to retain CoroutineScope through config changes without ViewModel

I know the recommendation is to use a ViewModel with our Activity, so we can use its viewModelScope. Since the ViewModel outlives the Activity, we don't have to cancel our jobs in activity.onDestroy().

However, sometimes you have a dirt-simple Activity. For example, it could populate a list view with filtered packages that are installed. You can very simply create a scope for the activity using a delegate, and cancel jobs in onDestroy():

class MyActivity(): AppCompatActivity(), CoroutineScope by MainScope() {

    private val listAdapter = MyAdapter()

    override fun onCreate() {
        super.onCreate()
        setContentView(R.layout.my_activity)
        recycler_view.apply {
            layoutManager = LinearLayoutManager(this)
            adapter = listAdapter
        }
        launch {
            val packages = getOrgPackagesWithIcons()
            adapter.apply {
                data = packages
                notifyDataSetChanged()
            }
        }

    }

    override fun onDestroy() {
        super.onDestroy()
        cancel() // CoroutineContext
    }

    private suspend fun getOrgPackagesWithIcons() = withContext(Dispatchers.Default) {
        var toNextYield = 20
        packageManager.getInstalledPackages(0)
            .filter { it.packageName.startsWith("org")
            .take(100)
            .map { 
                     if (--toNextYield == 0) { // Make it cancellable
                         toNextYield = 20
                         yield()
                     }
                     MyPackageData(
                         it.applicationInfo.loadLabel(packageManager).toString(),
                         it.packageName,
                         it.applicationInfo.loadIcon(packageManager)
                     )
                 }
        }

}

For a case like this, ViewModel feels like overkill. It would just be another layer to abstract the PackageManager, which is really a view model in itself.

The above code makes it easy to assemble the data in the background. The problem is that when the screen is rotated, or during other configuration changes, the coroutine is cancelled and restarted. Is there a clean recipe for keeping a CoroutineScope alive through a configuration change for a very simple Activity like this?

onRetainNonConfigurationInstance() is deprecated. I suppose we could put it in a Fragment and use retainInstance = true, but introducing a Fragment layer to such a simple Activity also feels like overkill.

Maybe there's a way to create an empty ViewModel implementation just so we can borrow its scope?

like image 414
Tenfour04 Avatar asked Jan 23 '20 15:01

Tenfour04


1 Answers

For a case like this, ViewModel feels like overkill.

I would argue otherwise and still suggest this would be a good use case for AndroidViewModel.

I believe that it is not the Activity's responsibility to fetch the package list just because it has access to the PackageManager. The Activity should only be responsible for displaying the list.

Using AndroidViewModel gives you access to Context and viewModelScope within your ViewModel instance.

like image 193
Emmanuel Avatar answered Nov 14 '22 11:11

Emmanuel