Home > database >  Not able to increment a counter variable inside my viewmodel
Not able to increment a counter variable inside my viewmodel

Time:10-04

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()

  • Related