Reputation: 93659
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?
Upvotes: 5
Views: 1576
Reputation: 2972
Agree with the above answer that ViewModel is still the best choice here because you are performing data operations which don't really belong in the activity. That said, if you look into the how ViewModel
retains data - its simple static. Not quoting the full chain here, just the part where it all comes together
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
where NonConfigurationInstances is:
static final class NonConfigurationInstances {
Object custom;
ViewModelStore viewModelStore;
}
Putting it here to address your question re "empty ViewModel implementation" - that's how it done there, and you can do something similar - perhaps place into your own Application class. But again that said, it's there and it's simple to use and provides other benefits... so I always use view model/androidviewmodel and there is no overhead at all in my mind, just the opposite, the code is nicely organized.
Upvotes: 0
Reputation: 13223
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.
Upvotes: 4