I have a view model that is data binded to a fragment. The view model is shared with the main activity.
I've button is binded to the view as follows:
<Button
android:id="@+id/startStopBtn"
android:text="@{dashboardViewModel.startStopText == null ? @string/startBtn : dashboardViewModel.startStopText}"
android:onClick = "@{() -> dashboardViewModel.onStartStopButton(context)}"
android:layout_width="83dp"
android:layout_height="84dp"
android:layout_gravity="center_horizontal|center_vertical"
android:backgroundTint="@{dashboardViewModel.isRecStarted == false ? @color/startYellow : @color/stopRed}"
tools:backgroundTint="@color/startYellow"
android:duplicateParentState="false"
tools:text="START"
android:textColor="#FFFFFF" />
What I expect to happen is that every time I press the button the function onStartStopButton(context) runs. This works fine as long as I don't rotate the device. When I rotate the device the function is run twice, if I rotate again the function is run 3 times and so on. This is not a problem if I go to another fragment and then back to the dashboard fragment. It looks like the live data observer is getting registered every time I rotate my screen, but not every time I detach and reattach the fragment.
This is true for all the elements in that fragment, whether they are data binded or I manually observe them.
Fragment code:
class DashboardFragment : Fragment() {
private var _binding: FragmentDashboardBinding? = null
private val binding get() = _binding!!
private val dashboardViewModel: DashboardViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentDashboardBinding.inflate(inflater, container, false)
val root: View = binding.root
binding.dashboardViewModel = dashboardViewModel
binding.lifecycleOwner = viewLifecycleOwner
dashboardViewModel.bleSwitchState.observe(viewLifecycleOwner, Observer { switchState -> handleBleSwitch(switchState) })
dashboardViewModel.yLims.observe(viewLifecycleOwner, Observer { yLims ->
updatePlotWithNewData(yLims.first, yLims.second)
})
Timber.i("Dahsboard on create: DashboardViewModel in fragment: $dashboardViewModel")
return root
}
}
The view model:
class DashboardViewModel : ViewModel() {
//region live data
private var _isRecStarted = MutableLiveData<Boolean>()
val isRecStarted: LiveData<Boolean> get() = _isRecStarted
//private var _bleSwitchState = MutableLiveData<Boolean>()
val bleSwitchState = MutableLiveData<Boolean>()
private var _startStopText = MutableLiveData<String>()
val startStopText: LiveData<String> get() = _startStopText
private var _yLims = MutableLiveData<Pair<kotlin.Float,kotlin.Float>>()
val yLims: LiveData<Pair<kotlin.Float,kotlin.Float>> get() = _yLims
//endregion
init {
Timber.d("DashboardViewModel created!")
bleSwitchState.value = true
}
//region start stop button
fun onStartStopButton(context: Context){
Timber.i("Start stop button pressed, recording data size: ${recordingRawData.size}, is started: ${isRecStarted.value}")
isRecStarted.value?.let{ isRecStarted ->
if (!isRecStarted){ // starting recording
_isRecStarted.postValue(true)
_startStopText.postValue(context.getString(R.string.stopBtn))
startDurationTimer()
}else{ // stopping recording
_isRecStarted.postValue(false)
_startStopText.postValue(context.getString(R.string.startBtn))
stopDurationTimer()
}
} ?: run{
Timber.e("Error! Is rec started is not there for some reason")
}
}
}
The view model is created the first time from the MainActivity as follows:
class MainActivity : AppCompatActivity() {
private val dashboardViewModel: DashboardViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
Timber.i("DashboardViewModel in main activity: $dashboardViewModel")
}
}
Edit explaining why the MainActivity is tided to the ViewModel:
The reason why the ViewModel is linked to the main activity is that the main activity handles some Bluetooth stuff for a stream of data, when a new sample arrives then the logic to handle it and update the UI of the dashboard fragment is on the DashboardViewModel. The data still needs to be handled even if the dashboard fragment is not there. So I need to pass the new sample to the DashboardViewModel from the main activity as that is where I receive it. Any suggestions to make this work?