Android Studio Chronometer Rotation - Works in portrait but not in landscape... using view models & Kotlin

Viewed 113

The chronometer is working in portrait. The issue is when it is rotated to landscape it doesn't continue from where it was in portrait... but the interesting thing is when it goes back to portrait (portrait -> landscape -> portrait) the chronometer picks up from where it was last.

Please help find the bug in the code... Thanks!

Here is the fragment code (NotesFragment.kt):

package com.genauapps.cis2818_proj4_runningapp

import android.os.Bundle
import android.os.SystemClock
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Chronometer
import androidx.lifecycle.ViewModelProvider
import com.genauapps.cis2818_proj4_runningapp.databinding.FragmentNotesBinding
import kotlin.math.absoluteValue

class NotesFragment : Fragment() {
    private lateinit var binding: FragmentNotesBinding
    private lateinit var viewModel: NotesViewModel

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentNotesBinding.inflate(layoutInflater)
        viewModel = ViewModelProvider(this).get(NotesViewModel::class.java)

        binding.notesFragmentBtStartStop.setOnClickListener { onStartStop(viewModel.time.value!!) }

        if (viewModel.isWorking.value == true) {
            binding.notesFragmentChTime.base = viewModel.time.value!!
            binding.notesFragmentChTime.start()
        }

        // Inflate the layout for this fragment
        return binding.root
    }

    private fun onStartStop(startTime: Long) {
        if (!viewModel.isWorking.value!!) {
            if (startTime == 0L) {
                binding.notesFragmentChTime.base = SystemClock.elapsedRealtime()
            } else {
                binding.notesFragmentChTime.base = startTime
            }
            binding.notesFragmentChTime.start()
            viewModel.isWorking.value = true
        } else {
            binding.notesFragmentChTime.stop()
            viewModel.isWorking.value = false
            viewModel.time.value = SystemClock.elapsedRealtime() - binding.notesFragmentChTime.base
            Log.i("test", "Stopwatch time is ${viewModel.time.value}")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        viewModel.time.value = SystemClock.elapsedRealtime() - binding.notesFragmentChTime.base

    }

//    override fun onDestroyView() {
//        super.onDestroyView()
//        viewModel.time.value = SystemClock.elapsedRealtime() - binding.notesFragmentChTime.base
//    }
}

Here is the view model code (NotesViewModel.kt):

package com.genauapps.cis2818_proj4_runningapp

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class NotesViewModel : ViewModel() {
    private val _time = MutableLiveData<Long>()
    val time: MutableLiveData<Long> get() = _time

    private val _date = MutableLiveData<String>()
    val date: LiveData<String> get() = _date

    private val _distance = MutableLiveData<Double>()
    val distance: LiveData<Double> get() = _distance

    private val _notes = MutableLiveData<String>()
    val notes: LiveData<String> get() = _notes

    private val _isWorking = MutableLiveData<Boolean>()
    val isWorking: MutableLiveData<Boolean> get() = _isWorking


    init {
        _time.value = 0L
        _isWorking.value = false
    }

    fun timer(time: Long) {

    }
}

Here is the xml (fragment_notes.xml):

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp"
    tools:context=".NotesFragment"
    android:name="androidx.navigation.fragment.MainFragment"
    >

    <TextView
        android:id="@+id/notes_fragment_tv_myworkout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:fontFamily="monospace"
        android:text="@string/text_view_myworkout"
        android:textSize="20sp"
        android:textStyle="bold|italic"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Chronometer
        android:id="@+id/notes_fragment_ch_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:ems="10"
        android:fontFamily="monospace"
        android:hint="@string/edit_text_time"
        android:textAlignment="center"
        android:textColor="@color/black"
        android:textColorHint="@color/black"
        android:textSize="34sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/notes_fragment_tv_myworkout"
        tools:ignore="TextContrastCheck" />

    <EditText
        android:id="@+id/notes_fragment_et_date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:ems="10"
        android:fontFamily="monospace"
        android:hint="@string/edit_text_date"
        android:importantForAutofill="no"
        android:inputType="date"
        android:minHeight="48dp"
        android:textColorHint="@color/black"
        android:textSize="20sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/notes_fragment_ch_time" />

    <EditText
        android:id="@+id/notes_fragment_et_distance"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:ems="10"
        android:fontFamily="monospace"
        android:hint="@string/edit_text_distance"
        android:inputType="numberDecimal"
        android:textColorHint="@color/black"
        android:textSize="20sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/notes_fragment_et_date"
        android:importantForAutofill="no" />

    <EditText
        android:id="@+id/notes_fragment_et_notes"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:ems="10"
        android:fontFamily="monospace"
        android:hint="@string/edit_text_notes"
        android:importantForAutofill="no"
        android:inputType="textCapSentences|textAutoCorrect"
        android:minHeight="48dp"
        android:textColor="@color/black"
        android:textColorHint="@color/black"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/notes_fragment_et_distance" />

    <Button
        android:id="@+id/notes_fragment_bt_start_stop"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:backgroundTint="@color/y_in_mn_blue"
        android:fontFamily="monospace"
        android:text="@string/button_text"
        android:textAlignment="center"
        android:textSize="18sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/notes_fragment_et_notes" />

    <Button
        android:id="@+id/notes_fragment_bt_save"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:backgroundTint="@color/y_in_mn_blue"
        android:fontFamily="monospace"
        android:text="@string/button_text_save"
        android:textAlignment="center"
        android:textSize="18sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/notes_fragment_bt_start_stop" />

</androidx.constraintlayout.widget.ConstraintLayout>
1 Answers

First of all, in the AndroidManifest file inside your "Activity tag" write:

android:configChanges="orientation|screenSize"

And now in NotesFragment.kt:

package com.genauapps.cis2818_proj4_runningapp

import android.os.Bundle
import android.os.SystemClock
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import com.genauapps.cis2818_proj4_runningapp.databinding.FragmentNotesBinding

class NotesFragment : Fragment() {
      private lateinit var binding: FragmentNotesBinding
      private lateinit var viewModel: NotesViewModel


override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?

): View? {
    binding = FragmentNotesBinding.inflate(layoutInflater)
    viewModel = ViewModelProvider(this).get(NotesViewModel::class.java)

    binding.notesFragmentBtStartStop.setOnClickListener { 
    onStartStop(viewModel.time.value!!) }

    // Inflate the layout for this fragment
    return binding.root

}

private fun onStartStop(startTime: Long) {
    if (viewModel.isWorking.value == false) {

        if (startTime == 0L) {
            binding.notesFragmentChTime.base = SystemClock.elapsedRealtime() - 
startTime/ SystemClock.elapsedRealtime()
            binding.notesFragmentChTime.start()
            viewModel.isWorking.value = true
        } else {
            binding.notesFragmentChTime.base = SystemClock.elapsedRealtime() - 
viewModel.time.value!!
            binding.notesFragmentChTime.start()
            viewModel.isWorking.value = true

        }

    } else {
        binding.notesFragmentChTime.stop()
        viewModel.isWorking.value = false
        viewModel.time.value = SystemClock.elapsedRealtime() - 
binding.notesFragmentChTime.base
    }
}

// Saving Fragment State When Orientation Changes
private fun Fragment.onRestoreInstanceState(savedInstanceState: Bundle?) {
    onRestoreInstanceState(savedInstanceState)
    onCreate(savedInstanceState)
   }
}

Remember, every time you change orientation, In fact, you are calling "Destroy()", and recreating the View, so you have to "save Fragment state" before switching between Portrait and Landscape orientation.

Related