1

I've switched from using LiveData to StateFlow in my app and also applied it in Data Binding. But after changing the value of StateFlow, the UI is not updating. From what I understand from the StateFlow docs, I only have to assign a new value to the value property of a StateFlow to update its consumers.

Below, I have a time field which I update the value by using an MaterialTimePicker dialog. But after I change the time and assign a new value to the selectedAlarm property (StateFlow) of my viewmodel, the UI is not updating for the new value. I'm not sure where is my mistake or what is lacking.

Below is my AlarmFormFragment:

@AndroidEntryPoint
class AlarmFormFragment : Fragment() {
    private val alarmsViewModel: AlarmsViewModel by activityViewModels()
    private var _binding: FragmentAlarmFormBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentAlarmFormBinding.inflate(inflater, container, false)
        binding.alarmsViewModel = alarmsViewModel
        binding.lifecycleOwner = viewLifecycleOwner
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        setUpTimeField()
    }

    private fun setUpTimeField() {
        alarmsViewModel.selectedAlarm.value.let { alarm ->
            binding.llTimeField.setOnClickListener {
                // Create and then show time picker
                val picker =
                    MaterialTimePicker.Builder()
                        .setTimeFormat(TimeFormat.CLOCK_12H)
                        .setTitleText("Set Time")
                        .setHour(alarm.time.get(Calendar.HOUR_OF_DAY))
                        .setMinute(alarm.time.get(Calendar.MINUTE))
                        .build()

                picker.addOnPositiveButtonClickListener {
                    alarm.time.set(Calendar.HOUR_OF_DAY, picker.hour)
                    alarm.time.set(Calendar.MINUTE, picker.minute)
                    alarmsViewModel.selectedAlarm.value = alarm
                }

                picker.show(childFragmentManager, "TIME_INPUT_DIALOG")
            }
        }
    }
}

Below is the xml file for AlarmFormFragment:

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <import type="com.example.myclock.utilities.Utility" />
        <import type="com.example.myclock.utilities.SnoozeUtils"/>
        <variable
            name="alarmsViewModel"
            type="com.example.myclock.viewmodels.AlarmsViewModel" />
    </data>

    <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"
        tools:context=".fragments.AlarmFormFragment">

        <!-- Form Fields -->
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <!-- Time Field -->
            <LinearLayout
                android:id="@+id/llTimeField"
                style="@style/FormFieldLayoutStyle">

                <TextView
                    style="@style/FormFieldTextStyle.Label"
                    android:text="@string/label_time" />

                <TextView
                    android:id="@+id/tvTime"
                    style="@style/FormFieldTextStyle.Value"
                    android:text="@{Utility.INSTANCE.timeToString(alarmsViewModel.selectedAlarm.value.time)}" />
            </LinearLayout>
        </LinearLayout>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

And below is my AlarmsViewModel:

@HiltViewModel
class AlarmsViewModel @Inject constructor(
    private val alarmsRepository: AlarmsRepository
) : ViewModel() {
    private val _alarms = MutableStateFlow<List<Alarm>>(emptyList())
    val alarms: StateFlow<List<Alarm>> get() = _alarms
    var selectedAlarm = MutableStateFlow(Alarm())

    ...
}

Below is class for the data Alarm:

@Parcelize
@Entity
class Alarm(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    var time: Calendar = Calendar.getInstance(),
    ...
) : Parcelable {
    ...
}
Al Ryan Acain
  • 477
  • 8
  • 17

0 Answers0