for some reason I can't understand why the method "getLifecycle()" from my class project.vasile.emanuel.gresanu.cemdmobilediagnosis2.ui.base.BaseFragment is being called multiple times when I put the app in the background. More exactly, run the code as it is ( I had made this special case for you to be tested ) and put the app in the background. From here the logcat is being filed with the message that "getLifecycle()" is being called to infinite. The link to the app: https://drive.google.com/file/d/1xZ8unSEwgkumPRUj2BpwsL9hKk1XJLrU/view?usp=sharing
Please help
The relevant class is project.vasile.emanuel.gresanu.cemdmobilediagnosis2.ui.base.BaseFragment because I am implementing LifecycleOwner, and overriding the method getLifecycle().
Code:
abstract class BaseFragment: dagger.android.support.DaggerFragment(), LifecycleOwner {
var lifeCycleRegistry: LifecycleRegistry
init {
AppLogger.d("BaseFragment.init")
this.lifeCycleRegistry = LifecycleRegistry(this#BaseFragment)
}
//....
override fun getLifecycle(): Lifecycle {
AppLogger.d("BaseFragment.getLifecycle and count: ${this.lifeCycleRegistry.observerCount}, ${this.lifeCycleRegistry.currentState}")
return this.lifeCycleRegistry
}
}
When I put the app in the background, the method "getLifecycle()" is being called to infinite.
Solved the problem of LifecycleOwner.getLifecycle() being called multiple times. All I did was to comment the overriding of the method in my base class:
abstract class BaseFragment: dagger.android.support.DaggerFragment(), LifecycleOwner {
var lifeCycleRegistry: LifecycleRegistry
init {
AppLogger.d("BaseFragment.init")
this.lifeCycleRegistry = LifecycleRegistry(this#BaseFragment)
}
/*override fun getLifecycle(): Lifecycle {
AppLogger.d("BaseFragment.getLifecycle and count: ${this.lifeCycleRegistry.observerCount}, ${this.lifeCycleRegistry.currentState}")
return this.lifeCycleRegistry
}*/
}
And now it works like a charm
Related
I'm subscribed to an observable in my Fragment, the observable listens for some user input from three different sources.
The main issue is that once I navigate to another Fragment and return to the one with the subscription, the data is duplicated as the observable is handled twice.
What is the correct way to handle a situation like this?
I've migrated my application to a Single-Activity and before it, the subscription was made in the activity without any problem.
Here is my Fragment code:
#AndroidEntryPoint
class ProductsFragment : Fragment() {
#Inject
lateinit var sharedPreferences: SharedPreferences
private var _binding: FragmentProductsBinding? = null
private val binding get() = _binding!!
private val viewModel: ProductsViewModel by viewModels()
private val scanner: CodeReaderViewModel by activityViewModels()
private fun observeBarcode() {
scanner.barcode.observe(viewLifecycleOwner) { barcode ->
if (barcode.isNotEmpty()) {
if (binding.searchView.isIconified) {
addProduct(barcode) // here if the fragment is resumed from a backstack the data is duplicated.
}
if (!binding.searchView.isIconified) {
binding.searchView.setQuery(barcode, true)
}
}
}
}
private fun addProduct(barcode: String) {
if (barcode.isEmpty()) {
return
}
viewModel.insert(barcode)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.start(args.documentId)
if (args.documentType == "Etichette") {
binding.cvLabels.visibility = View.VISIBLE
}
initUI()
observe()
}
private fun observe() {
observeBarcode()
observeProducts()
observeLoading()
observeLast()
}
}
Unfortunately, LiveData is a terribly bad idea (the way it was designed), Google insisted till they kinda phased it out (but not really since it's still there) that "it's just a value holder"...
Anyway... not to rant too much, the solution you have to use can be:
Use The "SingleLiveEvent" (method is officially "deprecated now" but... you can read more about it here).
Follow the "official guidelines" and use a Flow instead, as described in the official guideline for handling UI Events.
Update: Using StateFlow
The way to collect the flow is, for e.g. in a Fragment:
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) { // or RESUMED
viewModel.yourFlow.collectLatest { ... } // or collect { ... }
}
}
For that in your ViewModel you'd expose something like:
Warning: Pseudo-Code
// Imagine your state is represented in this sealed class
sealed class State {
object Idle: State
object Loading: State
data class Success(val name: String): State
data class Failure(val reason: String): State
}
// You need an initial state
private val _yourFlow = MutableStateFlow(State.Idle)
val yourFlow: StateFlow<State> = _yourFlow
Then you can emit using
_yourFlow.emit(State.Loading)
Every time you call
scanner.barcode.observe(viewLifecycleOwner){
}
You are creating a new anonymous observer. So every new call to observe will add another observer that will get onChanged callbacks. You could move this observer out to be a property. With this solution observe won't register new observers.
Try
class property
val observer = Observer<String> { onChanged() }
inside your method
scanner.barcode.observe(viewLifecycleOwner, observer)
Alternatively you could keep your observe code as is but move it to a Fragment's callback that only gets called once fex. onCreate(). onCreate gets called only once per fragment instance whereas onViewCreated gets called every time the fragment's view is created.
We have a task that is run when the Application is created and we're trying to move the code from our Application object's onCreate to their own Lifecycle aware classes. I'm added my ApplicationLifecycleAwareTaskRunner (a LifecycleObserver) to the lifecycle of the ProcessLifecycleOwner in Application.onCreate() but it's onCreate(owner: LifecycleOwner) is never called. The onStart(..) and onStop() are called as expected.
Is this a known limitation of LifecycleObserver that it cannot observe Application.onCreate() events? or is there something I'm missing here?
Using androidx.lifecycle:lifecycle-runtime-ktx:2.4.1
Adding the observer in the Application object.
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// DI and other init
ProcessLifecycleOwner.get().lifecycle.addObserver(ApplicationLifecycleAwareTaskRunner(..))
}
}
The task runner:
class ApplicationLifecycleAwareTaskRunner(
private val appCoroutineScope: CoroutineScope,
private val myTask: MyTask
) : DefaultLifecycleObserver {
// This is never called :(
override fun onCreate(owner: LifecycleOwner) {
appCoroutineScope.launch {
myTask.invoke()
}
}
...
}
I'm looking for a way to combine different features in an Android activity, that should be reusable for different activity classes. Specifically the problem arises from overriding open methods where the super's implementation also has to be called.
open class FirstActivity : FragmentActicity() {
override fun onStart() {
super.onStart()
doSomething()
}
}
That's simple enough, but it is not reusable. I could e.g. want to have the same behavior with a different base activity class:
open class SecondActivity : AppCompatActivity() {
override fun onStart() {
super.onStart()
doSomething()
}
}
where I'd have to duplicate the code. If I have a very basic functionality like tracking the state of the activity, I would want this in more or less all of my activities which do have different base classes.
It get's even worse when I want to create some more features that can be combined:
open class ThirdActivity : FragmentActivity() {
override fun onResume() {
super.onResume()
doSomeResuming()
}
}
open Class FirstActivityAgain : ThirdActivity {
override fun onStart() {
super.onStart()
doSomething()
}
}
class MyFragmentActivity : FirstActivity() {
override fun onStop() {
doSomethingElse()
super.onStop()
}
}
class MyFragmentActivityWithResuming : FirstActivityAgain() {
override fun onStop() {
doSomethingElse()
super.onStop()
}
}
class MyTopBarActivity : SecondActivity() {
override fun onStop() {
doSomethingElse()
super.onStop()
}
}
In Scala I can use Traits to do this stackable modification, which allows for very flexible mixins of functionality. It's even possible to modify the same method over and over again, one just has to be careful with the linearization order.
None of this is possible in Kotlin because a Scala Trait is neither equivalent to a Kotlin abstract class nor to a Kotlin Interface.
It doesn't seem to be possible with Kotlin's delegates either. I also thought about using generics, which in my limited imagination could look like this:
open class FirstActivity<BaseActivity : Activity> : BaseActivity() {
...
}
which of course is also not possible.
Is there anything I've overlooked? Can it be done by using Dagger?
What you are referring to in Kotlin called interfaces in conjunction with some basic delegation.
interface Base {
fun printMessage()
fun printMessageLine()
}
class BaseImpl(val x: Int) : Base {
override fun printMessage() { print(x) }
override fun printMessageLine() { println(x) }
}
class Derived(b: Base) : Base by b {
override fun printMessage() { print("abc") }
}
fun main() {
val b = BaseImpl(10)
Derived(b).printMessage()
Derived(b).printMessageLine()
}
Though it won't save you from the super problem since it Android framework issue rather than Kotlin Language.
For your case I would do something like
interface BaseActivityContainer{
var activity: Activity
}
class MainActivity: BaseActivityContainer{
override var activity: Activity = this
}
interface BaseDoable: BaseActivityContainer{
fun doActivityStuff(){
activity.getString(...)
}
}
interface BaseDoableSecond: BaseActivityContainer{
fun doActivityStuff(){
activity.getDrawable(...)
}
}
class SomeActivity: MainActivity, BaseDoableSecond by this
Handle Lyfecycle events with the help of Android Lifecycle
This is not complete and barely functional but I hope it will clear some stuff for you.
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.
I'm trying to create an app which will use MVVM architecture and there's one thing I quite don't understand.
Official Android docs say that's not a good idea to reference activity context in ViewModel's (as ViewModel may outlive activity) so I've started to wonder about usecase when I want to execute some action when my activity is resumed.
I know ViewModel's shouldn't do business logic themselves but even if I use some service class (let's say GPSService which has to start and pauseeach time activity is resumed on paused), and inside this service I react to activity onResume (using Lifecycle observer) I will still reference this activity from ViewModel as I'm referencing service which holds reference to activity being observed, this may cause activity leak (correct me if I'm wrong).
So my question is, how to react to activity or fragment lifecycle in MVVM architecture?
If you need to have a ViewModel be lifecycle aware, then you can have it implement LifeCycleObserver and override life cycle events as necessary. Example,
public class MyModel extends ViewModel implements
LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
protected void onLifeCycleStop() {
// do something
}
}
In the activity or fragment then you can add the view model to the activity life cycle owner.
public class MyActivity extends AppCompatActivity {
protected MyModel mMyModel;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMyModel = ViewModelProviders
.of(this)
.get(MyModel.class);
getLifecycle().addObserver(mMyModel);
}
}
I know ViewModel's shouldn't do business logic themselves
Yes, you're right. ViewModel should not contain business logic but
it should contain UI related logic. So basically, API calls or Some
location related stuffs should be avoided in ViewModel logic.
So what if you wanna make some scenario which can react to any activity lifecycle? I'll suggest you to use LifecycleObserver.
Why?, Because LifecycleObserver will provide you callbacks once it's LifecycleOwner will change it's state.
What is LifecycleOwner here? In our case it may be Activity/Fragment.
So, how you can achieve this?
Let's say you want to make location requests during resume & pause period of any activity.
So, for that you can create a class called LocationUpdates as LifecycleObserver like below:
class LocationUpdates : LifecycleObserver {
constructor(){
// some basic location related initialization here
}
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun connectListener() {
// this method will respond to resume event of our Lifecycle owner (activity/fragment in our case)
// So let's get location here and provide callback
}
#OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun disconnectListener() {
// this method will respond to pause event of our Lifecycle owner (activity/fragment in our case)
// So let's stop receiveing location updates here and remove callback
}
#OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) // Optional if you want to cleanup references
fun cleanUp() {
// this method will respond to destroy event of our Lifecycle owner (activity/fragment in our case)
// Clean up code here
}
}
Now from your activity, you can directly make your LocationUpdates, and receive callback.
class MyActivity : AppCompatActivity() {
private lateinit var mLocationUpdates: LocationUpdates
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Initialize your LifecycleObserver here & assign it to this activity's lifecycle
lifecycle.addObserver(mLocationUpdates)
}
}
You can refer to how to handle Lifecycle & Codelabs example.
Edit:
If you want to have ViewModel for that job, consider this:
class MyViewModel : ViewModel {
private lateinit var mLocationUpdates: LocationUpdates
constructor() : super() {
// initialize LocationUpdates here
}
// Assign our LifecyclerObserver to LifecycleOwner
fun addLocationUpdates(lifecycle: Lifecycle){
lifecycle.addObserver(mLocationUpdates)
}
//Optional, we really don't need this.
fun removeLocationUpdates(lifecycle: Lifecycle){
lifecycle.removeObserver(mLocationUpdates)
}
}
If your LocationUpdates depends upon Context, consider using AndroidViewModel.
We can now observe our location updates # any activity/fragment using LiveData, and assign our LifecycleObserver like below:
class MyActivity : AppCompatActivity() {
private val viewModel: MyViewModel by lazy {
return#lazy ViewModelProviders.of(this#MyActivity).get(MyViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.addLocationUpdates(lifecycle)
}
}
Please note: there's still lot to cover but making this answer as short as possible. So, if you're still confused about something related then please feel free to ask me in comment. I will edit my answer.
with java 8 LifecycleObserver has been deprecated. According to the [docs][1] it is not recommended to use this class as it uses reflection.
Rather the docs recommend using DefaultLifecycleObserver. To do that, extend your ViewModel class with DefaultLifecycleObserver like:
class MyViewModel : ViewModel(), DefaultLifecycleObserver {//implement default lifecycle observer
override fun onCreate(owner: LifecycleOwner) {//override lifecycle events
super.onCreate(owner)
}
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
}
}
and get all the lifecycle event callbacks in your viewmodel by registering your viewmodel as lifecycle event observer in your view class (e.g. Activity class) like:
class MyActivity : AppCompatActivity() {
private val myViewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
lifecycle.addObserver(splashViewModel)//registering observer
...
}
}
its just and update to the answer by #farid_z with kotlin and new sdk.
[1]: https://developer.android.com/reference/androidx/lifecycle/LifecycleObserver