I have an application where i'm binding my viewmodel to the main activity lifecycle and i do increment a counter based on which lifecyle has been called , but i'm not able to increment the counters variable , any reason why , Thank yo
- This is my viewmodel class
class CounterViewModel : ViewModel(), DefaultLifecycleObserver {
private var onCreateCounter = 0
private var onStartCounter = 0
private var onResumeCounter = 0
// This class defines the ViewModel which keeps track of the number of times onCreate(), onStart() and onResume() have been called.
companion object {
private const val TAG = "CounterViewModel"
}
// TODO :
// Create variables to keep a track of the number of times onCreate(), onStart() and onResume() have been called.
// To keep track of each count, define two variables as specified below.
// Define a private variable of type MutableLiveData that can only be modified within the ViewModel class.
// Define an internal/public variable of type LiveData that can be accessed externally by the UI/fragment but cannot be modified.
// Use a backing property to specify the getter function for the internal/public variable
// Refer to the link below for a more detailed explanation/example
// https://developer.android.com/codelabs/basic-android-kotlin-training-viewmodel#4
private val _onStartMutableLiveData : MutableLiveData<Int> = MutableLiveData(onStartCounter)
val onStartLiveData : LiveData<Int> get() = _onStartMutableLiveData
private val _onCreateMutableLiveData : MutableLiveData<Int> = MutableLiveData(onCreateCounter)
val onCreateLiveData : LiveData<Int> get() = _onCreateMutableLiveData
private val _onResumeMutableLiveData : MutableLiveData<Int> = MutableLiveData(onResumeCounter)
val onResumeLiveData : LiveData<Int> get() = _onResumeMutableLiveData
val onCreateProperty : LiveData<Int>
get() {
return onCreateLiveData
}
val onStartProperty : LiveData<Int>
get() {
return onStartLiveData
}
val onResumeProperty : LiveData<Int>
get() {
return onResumeLiveData
}
internal fun bindToActivityLifecycle(mainActivity: MainActivity) {
// TODO :
// Add the current instance of CounterViewModel as a LifeCycleObserver to the MainActivity
// Use the addObserver function
mainActivity.lifecycle.addObserver(this)
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
// Update the appropriate count variable
Log.i(TAG,"Entered onResume")
onResumeCounter
_onResumeMutableLiveData.value = onResumeCounter
}
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
// Update the appropriate count variable
Log.i(TAG,"Entered onCreate")
onCreateCounter
_onCreateMutableLiveData.value = onCreateCounter
}
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
// Update the appropriate count variable
Log.i(TAG,"Entered onStart")
onStartCounter
_onStartMutableLiveData.value = onStartCounter
}
}
** Fragment Code
class FirstFragment : Fragment() {
/** Binding to XML layout */
private lateinit var binding: FirstFragmentBinding
// Create a variable of type CounterViewModel to keep track of counts
private lateinit var viewModel: CounterViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Use the provided binding object to inflate the layout.
binding = FirstFragmentBinding.inflate(inflater, container, false)
// Update ActionBar label to distinguish which Fragment is displayed
(requireActivity() as AppCompatActivity).supportActionBar?.title = this.javaClass.simpleName
// Set onClickListener to navigate to the second fragmant from the first
binding.fab.setOnClickListener {
findNavController().navigate(FirstFragmentDirections.actionFirstFragmentToSecondFragment())
}
// Return the root view.
return binding.root
}
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// TODO:
// Initialize CounterViewModel instance
viewModel = ViewModelProvider(this)[CounterViewModel::class.java]
// TODO:
// Use binding to display initial counts
binding.onCreate.text = "onCreate Called : ${viewModel.onCreateLiveData.value}"
binding.onStart.text = "onStart Called : ${viewModel.onStartLiveData.value}"
binding.onResume.text = "onResume Called : ${viewModel.onResumeLiveData.value}"
// The function below updates the counts over time
beginObservingCounter()
}
@SuppressLint("SetTextI18n")
private fun beginObservingCounter() {
// TODO:
// Register observers for each of the count variables
// In the body of the observe function, update the text to be displayed by using the binding
viewModel.onStartLiveData.observe(viewLifecycleOwner){ counter ->
binding.onStart.text = "onStart Called Again $counter"
}
viewModel.onCreateLiveData.observe(viewLifecycleOwner){ counter ->
binding.onCreate.text = "onCreate Called Again $counter"
}
viewModel.onResumeLiveData.observe(viewLifecycleOwner){ counter ->
binding.onResume.text = "onResume Called Again $counter"
}
}
}
CodePudding user response:
You mentioned in a comment that an init block gets called when you rotate - which init block? Rotating the screen should give you the same ViewModel
instance with all its state intact, that's the point of them. Maybe you're initialising them the wrong way, maybe creating another copy and observing LiveData
from that instead of the instance that's actually watching your lifecycle and incrementing?
For what it's worth, your code works fine for me in an Activity
like this:
val counterViewModel: CounterViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
counterViewModel.bindToActivityLifecycle(this)
// onResumeProperty works too since it's the same thing!
counterViewModel.onResumeLiveData.observe(this) { count ->
Toast.makeText(this, "Resumes: $count", Toast.LENGTH_SHORT).show()
}
}
I get a Toast every time onResume
happens - well technically it happens more often since it observes the old data first when rotating, then gets the new value when you hit onResume
again, but it does work! So I'd make sure you're handling the right instances
CodePudding user response:
So, turns out that while the definition of operator fun inc()
is used for
, they do not do the same thing, 0.inc()
is valid, whereas 0
is not. Turns out that all inc
does is return the value plus 1 (see the docs) and
calls inc
but then also sets the new value. So, you can either change out for using
or do onStartCounter = onStartCounter.inc()