Kotlin: define a coroutine scope in App class - android

Is it a good practice to declare a coroutine scope inside the App class instead of using a Globascope?
class App : Application() {
val applicationJob = Job()
companion object {
lateinit var instance: App
private set
lateinit var appDefaultScope: CoroutineScope
}
override fun onCreate() {
super.onCreate()
instance = this#App
appDefaultScope = CoroutineScope(Dispatchers.Default + applicationJob)
}
}
Here I defined a variable appDefaultScope and initialized it in the App's onCreate
So anywhere in my code I could always do:
App.appDefaultScope.launch {
// some operations
}
The main difference with a GlobalScope that I see is that here I can always cancel the job and shut down all potentially stuck coroutines.
Are there are better alternatives to this?
The reason is that i have some object functions that use coroutines and are not called from an activity (then I cannot provide them with a scope defined inside the activity) but need a scope for their operations.
An example of such object function is a Log that writes on a local database that can be called almost everywhere in the App.

Related

How to share viewmodel between two fragments which belong to two different activities

I am trying to understand how can I use my same viewmodel across different fragments which even belongs to different activities.
So let's say I have Activity1 with Fragment A, Fragment B and Activity2 with Fragment C. How do I create a single instance of viewmodel that I can use across all these fragments.
I tried understanding shared viewmodel but seems like it is to be used if sharing data between fragments of a single activity and not multiple activities.
So basically I want to create a single instance of viewmodel across all the fragments? How can I achieve this functionality also keeping in mind the MVVM approach.
Any help would be appreciated. Thanks!
This is not supported. Google's recommendation is to put all your screens in a single Activity.
But, you can make an intermediate singleton class that each instance of the ViewModel uses.
Or maybe you could use a factory that treats it like a temporary singleton and does reference counting so it doesn't get cleared too early or hang onto the reference for too long. Untested example of what I mean:
private var viewModelInstance: MyViewModel? = null
private var refCount = 0
class MyViewModel: ViewModel() {
override fun onCleared() {
if (--refCount > 0) {
return
}
viewModelInstance = null
// Do typical onCleared cleanup here
}
}
class MyViewModelFactory: ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
require(modelClass.isAssignableFrom(MyViewModel::class.java)) { "Factory only supports MyViewModel" }
refCount++
viewModelInstance = viewModelInstance ?: MyViewModel()
return viewModelInstance as T
}
}
Shared viewmodel between multiple activities is not supported.
One way to achieve this using AndroidViewModel. You can create a ViewModel extending AndroidViewModel . This requires application instance. This viewmodel will be binded to application lifecycle and same instance will be available through out the lifecycle of the application. In one activity you can add data, and in other activity you can get updated data.
This will be acting something like singleton instance(But not exactly).
Addition to this, you can also use live data in AndroidViewModel if you use observer with activity/fragment lifecycle owner. So the observer will be live only till life cycle of fragment or activity.
ViewModel:
class AppViewModel constructor(private val mApplication: Application) :
AndroidViewModel(mApplication) {
//ViewModel Logic
}
Initializing Viewmodel:
You can initialize viewmodel like this in any fragment or activity.
val appViewModel = ViewModelProvider.AndroidViewModelFactory.getInstance(MyApp.getInstance())
.create(AppViewModel::class.java)
Application Class:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
mInstance = this
}
companion object {
lateinit var mInstance: CooperApp
#Synchronized
fun getInstance(): MyApp {
return mInstance
}
}
}
Also one more thing we can use is like initializing viewmodel in application class and create similar function to getInstance() which will return viewmodel instance and use it all over the app

Properly initialize heavy objects

I am trying to convert some Java code to Kotlin. I have a "heavy" object that I cannot reason about how to initialize properly in the app. The object can take some time to create and I don't want to block except for when the functionality is actually required. I wrote some code that meets my requirements, but it doesn't seem like a good pattern and I was hoping someone tell me what the proper pattern here (will list what I don't like about it after the code):
package com.example.myapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.coroutines.*
import javax.inject.Provider
import kotlinx.coroutines.channels.Channel
class MainActivity : AppCompatActivity() {
lateinit var heavyInitObject: Provider<HeavyInitObject>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
heavyInitObject = initHeavyObject()
}
fun useTheHeavyInitObject() {
// Need it to block here, before work is done.
val hio = heavyInitObject.get()
}
}
fun initHeavyObject(): Provider<HeavyInitObject> {
val cnl = Channel<HeavyInitObject>(Channel.BUFFERED)
//purposely block to ensure to ensure it is initialized
val provider = Provider { runBlocking { cnl.receive().also { cnl.close() }}}
HeavyInitObject.get(object : HeavyInitObject.HeavyInitObjectListener{
override fun onReady(heavyInitObject: HeavyInitObject) = runBlocking {
cnl.send(heavyInitObject)
}
})
return provider
}
// Mocked library I am using (i.e. I don't have control over the implementation)
class HeavyInitObject {
companion object {
fun get(listener: HeavyInitObjectListener) {
val heavyInitObject = HeavyInitObject()
listener.onReady(heavyInitObject)
}
}
interface HeavyInitObjectListener {
fun onReady(heavyInitObject: HeavyInitObject)
}
}
What I don't like
Should be a val
It naturally really be a val, because the value should never change once initialized.
class MainActivity : AppCompatActivity() {
val heavyInitObject: Provider<HeavyInitObject> = initHeavyObject()
// OR...
val heavyInitObject: HeavyInitObject by lazy {
initHeavyObject().get()
}
The first option seems like it could do too much too fast. Depending on how someone would add MainActivity to the object graph it could really affect startup performance.
The second one is too slow. If we haven't requested the heavy object to be created before it is needed, there will be definite jank in the application when the heavy object is queried the first time.
Is there a good way to have the object be a val while requesting the object to be created in onCreate (understanding that I don't have control over implementation of the underlying library)?
Is channel the right data structure here?
Maybe this is bareable, but I wanted to see if there is a better option. A RENDEZVOUS channel makes more sense, but send suspends until receive is called and I don't want to block anything on thread initializing the object (i.e. since i can't convert the implementation to a suspend function). Switching to a bufferend channel won't block since I only send one element through, but that seems like a hack. What is the best data structure for this task?
Edit:
Thanks to some help in the comments I have improved the second condition (eliminate akward use of channel). I have a couple ideas for how to improve the first condition...
Code for getting rid of channel
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class MainActivity : AppCompatActivity() {
lateinit var heavyInitObject: Provider<HeavyInitObject>
override suspend fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
heavyInitObject = lifecycleScope.async {deferredHeavyObject()}
}
fun useTheHeavyInitObject() {
// Need it to block here, before work is done.
val hio = heavyInitObject.await()
}
}
suspend fun initHeavyObject(): HeavyInitObject = suspendCancellableCoroutine { continuation ->
HeavyInitObject.get(object : HeavyInitObject.HeavyInitObjectListener {
override fun onReady(heavyInitObject: HeavyInitObject) {
continuation.resume(heavyInitObject)
}
})
}
Code to finalize heavyInitObject
class MainActivity : AppCompatActivity() {
val heavyInitObject by lazy { heavyInitObjectBackingField }
private lateinit var heavyInitObjectBackingField: Deferred<HeavyInitObject>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
heavyInitObjectBackingField = lifecycleScope.async { deferredHeavyObject()}
}}
I basically get a lateinit val that way... and can be confident I won't get an error for it not being initialized. Ideally, it makes me realize this is overcomplicated, because I can't get under the hood and easily seperate the object initialization from the call back initialization.. Unless anyone else has a better idea?
Channel is kind of weird for returning a single item. I would load it in a Deferred privately and publicly expose a suspend getter that awaits the Deferred result. Once the object is ready, it won’t have to be waited for. And since it’s a suspend function, you can access it via a coroutine without unlocking your main thread.
object HeavyObjectCreator {
private val heavyObject: Deferred<HeavyObject> = GlobalScope.async {
// Long running actions to generate the object…
HeavyObject(params)
}
suspend fun getInstance(): HeavyObject =
heavyObject.await()
}
In your activity you can use lifecycleScope.launch to start a coroutine when you need to do a task that uses the object and it can call the above function to get it in a suspending way. If you want to preload the heavy object, you can put the statement HeavyObjectCreator in onCreate of your Application class or your Activity so the creator object will be instantiated and start the coroutine to load the heavy object.
This is just one example of a way to do it for an object that you’ll be reusing on multiple screens. If you intend to load a new heavy object only on screens that need it, I’d consider putting the contents of the class above directly in a ViewModel and use viewModelScope instead of GlobalScope.

How to force initialization on App Context?

in order to use application context anywhere, I use this code:
class App : Application() {
companion object {
lateinit var instance: App private set
fun isInstanceInitialized() = ::instance.isInitialized
}
override fun onCreate() {
super.onCreate()
instance = this
}
}
But sometimes it throws an error.
kotlin.UninitializedPropertyAccessException: lateinit property instance has not been initialized
As suggested here, setting a delay (before calling App.instance) helps, but I would like to call something without having to wait for a set delay. And besides that, isInstanceInitialzed method always returns false..
What can I write in the beginning of onCreate method in MainActivity, in order to make sure that the instance variable is initialized?

Android ViewModelProvider() parameter error

I am trying to get a value from the SharedViewModel class but the ViewModelProvider() is giving a parameter error when i am passing requireActivity() although the same initilization and assignment works in my fragments.
It is requiring "ViewModelStoreOwner" to be passed.
class CourseRepository(val app: Application) {
private var viewModel: SharedViewModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
val courseData = MutableLiveData<List<Course>>()
init {
CoroutineScope(Dispatchers.IO).launch {
callWebService()
}
}
#WorkerThread
suspend fun callWebService() {
if (Utility.networkAvailable(app)) {
val retrofit = Retrofit.Builder().baseUrl(WEB_SERVICE_URL).addConverterFactory(MoshiConverterFactory.create()).build()
val service = retrofit.create(CourseService::class.java)
val serviceData = service.getCourseData(viewModel.pathName).body() ?: emptyList()
courseData.postValue(serviceData)
}
}
}
The purpose of the ViewModel here is because i am storing the Id of the selected RecyclerView item in order to send it to a server
ViewModel instances are scoped to Fragments or Activities (or anything with a similar lifecycle), which is why you need to pass in a ViewModelStoreOwner to the provider to get a ViewModel from it. The point of ViewModels is that they will exist until the store they belong to is destroyed.
The requireActivity method doesn't work here, because you're not inside a Fragment.
Some things to consider here:
Do you really need ViewModel in this use case? Could you perhaps use just a regular class that you can create by calling its constructor?
Could you call this Repository from your ViewModel, and pass in any parameters you need from there?

How to ensure ViewModel#onCleared is called in an Android unit test?

This is my MWE test class, which depends on AndroidX, JUnit 4 and MockK 1.9:
class ViewModelOnClearedTest {
#Test
fun `MyViewModel#onCleared calls Object#function`() = mockkObject(Object) {
MyViewModel::class.members
.single { it.name == "onCleared" }
.apply { isAccessible = true }
.call(MyViewModel())
verify { Object.function() }
}
}
class MyViewModel : ViewModel() {
override fun onCleared() = Object.function()
}
object Object {
fun function() {}
}
Note: the method is protected in superclass ViewModel.
I want to verify that MyViewModel#onCleared calls Object#function. The above code accomplished this through reflection. My question is: can I somehow run or mock the Android system so that the onCleared method is called, so that I don't need reflection?
From the onCleared JavaDoc:
This method will be called when this ViewModel is no longer used and will be destroyed.
So, in other words, how do I create this situation so that I know onCleared is called and I can verify its behaviour?
In kotlin you can override the protected visibility using public and then call it from a test.
class MyViewModel: ViewModel() {
public override fun onCleared() {
///...
}
}
I've just created this extension to ViewModel:
/**
* Will create new [ViewModelStore], add view model into it using [ViewModelProvider]
* and then call [ViewModelStore.clear], that will cause [ViewModel.onCleared] to be called
*/
fun ViewModel.callOnCleared() {
val viewModelStore = ViewModelStore()
val viewModelProvider = ViewModelProvider(viewModelStore, object : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = this#callOnCleared as T
})
viewModelProvider.get(this#callOnCleared::class.java)
//Run 2
viewModelStore.clear()//To call clear() in ViewModel
}
TL;DR
In this answer, Robolectric is used to have the Android framework invoke onCleared on your ViewModel. This way of testing is slower than using reflection (like in the question) and depends on both Robolectric and the Android framework. That trade-off is up to you.
Looking at Android's source...
...you can see that ViewModel#onCleared is only called in ViewModelStore (for your own ViewModels). This is a storage class for view models and is owned by ViewModelStoreOwner classes, e.g. FragmentActivity. So, when does ViewModelStore invoke onCleared on your ViewModel?
It has to store your ViewModel, then the store has to be cleared (which you cannot do yourself).
Your view model is stored by the ViewModelProvider when you get your ViewModel using ViewModelProviders.of(FragmentActivity activity).get(Class<T> modelClass), where T is your view model class. It stores it in the ViewModelStore of the FragmentActivity.
The store is clear for example when your fragment activity is destroyed. It's a bunch of chained calls that go all over the place, but basically it is:
Have a FragmentActivity.
Get its ViewModelProvider using ViewModelProviders#of.
Get your ViewModel using ViewModelProvider#get.
Destroy your activity.
Now, onCleared should be invoked on your view model. Let's test it using Robolectric 4, JUnit 4, MockK 1.9:
Add #RunWith(RobolectricTestRunner::class) to your test class.
Create an activity controller using Robolectric.buildActivity(FragmentActivity::class.java)
Initialise the activity using setup on the controller, this allows it to be destroyed.
Get the activity with the controller's get method.
Get your view model with the steps described above.
Destroy the activity using destroy on the controller.
Verify the behaviour of onCleared.
Full example class...
...based on the question's example:
#RunWith(RobolectricTestRunner::class)
class ViewModelOnClearedTest {
#Test
fun `MyViewModel#onCleared calls Object#function`() = mockkObject(Object) {
val controller = Robolectric.buildActivity(FragmentActivity::class.java).setup()
ViewModelProviders.of(controller.get()).get(MyViewModel::class.java)
controller.destroy()
verify { Object.function() }
}
}
class MyViewModel : ViewModel() {
override fun onCleared() = Object.function()
}
object Object {
fun function() {}
}
For Java, if you create your test class in the same package (within the test directory) as of the ViewModel class (here, MyViewModel), then you can call onCleared method from the test class; since protected methods are also package private.

Categories

Resources