1
votes

I ask you to answer some of my questions about MVVM

I am currently making an exercise diary app in which the main fragment shows all the latest created exercises in a list (RecyclerView) There is also an fragment where I create these exercises and a fragment of the calendar, where it is marked on the calendar with a red dot whether there was a workout on that day

I want to display under the calendar, a list of the exercises that I did that day when I click on a date.

How it is implemented now:

There is my Calendar Class. I use Materialcalendarview library for calendar

class CalendarFragment : Fragment(), OnDateSelectedListener, OnMonthChangedListener {

private lateinit var mViewModel: CalendarVIewModel
private lateinit var binding: CalendarBinding
private val dates = ArrayList<CalendarDay>()
private var mExercises = ArrayList<Exercise>()
private val adapter = CalendarAdapter()


override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    binding = CalendarBinding.inflate(inflater, container, false)
    binding.calendarView.setOnDateChangedListener(this)
    setupViewModel()
    observerLiveData()
    setupRV()
    return binding.root
}

override fun onDateSelected(
    widget: MaterialCalendarView,
    date: CalendarDay,
    selected: Boolean
) {
    adapter.setList(getExerciseListOnThisData(date))
}


private fun setTrainingsInCalendar(exercise: List<Exercise>) {
    val cal = Calendar.getInstance()
    for (i in exercise.indices) {
        cal.time = convertLongToTime(exercise[i].timestamp)
        val month = cal[Calendar.MONTH]
        val day = cal[Calendar.DAY_OF_MONTH]
        val year = cal[Calendar.YEAR]
        val temp: LocalDate = LocalDate.of(year, month + 1, day)
        val dayd = CalendarDay.from(temp)
       dates.add(dayd)
        mExercises.add(exercise[i])
    }
    binding.calendarView.addDecorator(EventDecorator(Color.RED, dates))
}

private fun setupRV(){
    binding.apply {
        calendarRecyclerView.layoutManager = LinearLayoutManager(context)
        calendarRecyclerView.adapter = adapter
    }
}


private fun setupViewModel() {
    mViewModel =
        ViewModelProvider(this).get(CalendarVIewModel::class.java)
}

private fun observerLiveData() {
    mViewModel.exercises.observe(viewLifecycleOwner){
        setTrainingsInCalendar(it)
    }
}

override fun onMonthChanged(widget: MaterialCalendarView?, date: CalendarDay?) {
}


private fun getExerciseListOnThisData(date: CalendarDay): List<Exercise>{
    val mExercisesList = ArrayList<Exercise>()
    for (i in mExercises.indices) {
        if(calendarDay(mExercises[i].timestamp) == date){
            mExercisesList.add(mExercises[i])
        }
    }
    return mExercisesList
}

In the fun onDateSelected, I have to pass to adapter a list of exercises for this day. The first question is : how to correct pass the list there, using MVVM pattern? Now when navigating to a fragment, I subscribe to my LiveDate <List > in the ViewModel of my fragment. LiveDate <List > I get from a request to Room

class CalendarVIewModel@ViewModelInject constructor(private val exercisesRepository: ExercisesRepository)
: ViewModel() {
val exercises: LiveData<List<Exercise>> =
    exercisesRepository.allExercises

And when I subscribing to LiveData, I also in fun setTrainingsInCalendar inflate my

private var mExercises = ArrayList<Exercise>()

The second question is, how correct is it to do this? Perhaps it is better to implement all this in the ViewModel? Also the third question: How correct is it to make a request in the Room DAO of an already prepared List . For example, to pass a ready-made list of exercises, instead of subscribing to LiveData, for example, like this:

@Query("SELECT * FROM Exercises)
fun getAllExercises(): List<Exercise>>

setTrainingsInCalendar(exercise: List<Exercise> get from DAO Room)

Thank you. Im just beginner in MVVM

1
I ask you to answer some of my questions i don't think this is the best approach to getting answers on SO, you'd be better off asking ONE specific question at a time, that's just my advice - a_local_nobody

1 Answers

1
votes

Disclaimer: This answer is based on my opinion.

Let's start with the last question. If the table in the database does not change during the time that the user spends on the screen in question there is no need to return LiveData in your DAO class.

@Query("SELECT * FROM Exercises)
fun getAllExercises(): List<Exercise>>

Having said this, if you do return the List directly you will have to do so in the background thread and so you will need to use some kind of observer/event pattern so that the view is notified that the data is available. So you might as well stick to the current implementation.

Now, the view class (i.e. the Fragment) should be not dealing with logic. That should be the responsibility of the ViewModel. Something like so:

class CalendarViewModel@ViewModelInject constructor(
    private val exercisesRepository: ExercisesRepository
): ViewModel() {
    private val exercises: LiveData<List<Exercise>> = exercisesRepository.allExercises

    val exerciseInstants = Transformations.map(exercises) { exercises ->
        exercises.map { Instant.parse(it.timestamp) }
    }

    fun getExercisesForDate(date: Instant): List<Exercise> {
        return requireNotNull(exercises.value).filter {
            Instant.parse(it.timestamp) == date
        }
    }
}

Note that I have used Instant instead of LocalDateTime/CalenderDay for explanation's sake. Also, you should not use view classes (i.e. CalendarDay) in the ViewModel, LocalDateTime is fine. You might want to use another library for the Calendar view someday and that should not affect your ViewModel.

Now in the fragment, you can subscribe to exerciseInstants and set the dates for the calendar and you can call the ViewModel method getExercisesForDate to get exercises for a particular date.

override fun onDateSelected(
    widget: MaterialCalendarView,
    date: CalendarDay,
    selected: Boolean
) {
    adapter.setList(mViewModel.getExercisesForDate(...)))
}

private fun setBindings() {
    mViewModel.exerciseInstants.observe(viewLifecycleOwner) {
        setTrainingsInCalendar(...)
    }
}

Don't create fields like dates, mExercises in the Fragment. Use data from the ViewModel