How do I persist changes in RecyclerView order with Room and LiveData?

Viewed 1021

I'm having trouble figuring out how I'm supposed to update data in a Room Database after a change in RecyclerView item order. How am I supposed to update LiveData items in Room based on user action?

Using ItemTouchHelper.Callback I've set up an onMove callback that can make changes to the order of items presented to the user (on drag and drop), but when I make a call to update the order of items in the Room Database, using a ViewModel object, the user can then only move items one at a time. So if you drag an item, it will only move one space.

This is the onMove function I have defined in the ListAdapter, which implements ItemTouchHelperAdapter for the callback.

override fun onMove(
        recyclerView: RecyclerView,
        fromViewHolder: RecyclerView.ViewHolder,
        toViewHolder: RecyclerView.ViewHolder
    ): Boolean {

        d(this.TAG, "swap viewHolders: " + fromViewHolder.adapterPosition + " to " + toViewHolder.adapterPosition)


        val workoutRoutine1 = workoutRoutines[fromViewHolder.adapterPosition]
        val workoutRoutine2 = workoutRoutines[toViewHolder.adapterPosition]

        workoutRoutine1.orderNumber = toViewHolder.adapterPosition.toLong()
        workoutRoutine2.orderNumber = fromViewHolder.adapterPosition.toLong()

        //this.workoutRoutinesViewModel.update(workoutRoutine1)
        //this.workoutRoutinesViewModel.update(workoutRoutine2)

        notifyItemMoved(fromViewHolder.adapterPosition, toViewHolder.adapterPosition)
        return true
    }

This is my DAO object

@Dao
interface WorkoutRoutineDAO {
    @Insert
    suspend fun insert(workoutRoutine: WorkoutRoutine)

    @Update(onConflict = OnConflictStrategy.REPLACE)
    suspend fun update(workoutRoutine: WorkoutRoutine)

    @Delete
    suspend fun delete(workoutRoutine: WorkoutRoutine)

    @Query("DELETE FROM workout_routine_table")
    fun deleteAll()

    @Query("SELECT * FROM workout_routine_table ORDER BY order_number ASC")
    fun getAllWorkoutRoutines(): LiveData<List<WorkoutRoutine>>

    @Query("SELECT COALESCE(MAX(order_number), -1) FROM workout_routine_table")
    fun getLargestOrderNumber(): Long
}

This is my RoomDatabase object

@Database(entities = [WorkoutRoutine::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun workoutRoutineDAO(): WorkoutRoutineDAO

    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(
            context: Context,
            scope: CoroutineScope
        ): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance =
                    Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database")
                        .addCallback(WorkoutRoutineDatabaseCallback(scope))
                        .build()
                INSTANCE = instance
                instance
            }
        }
    }

    private class WorkoutRoutineDatabaseCallback(
        private val scope: CoroutineScope
    ) : RoomDatabase.Callback() {

    }
}

This is the ViewModel object I implemented.

class WorkoutRoutinesViewModel(application: Application) : AndroidViewModel(application) {
    private val workoutRoutinesRepository: WorkoutRoutineRepository

    val allWorkoutRoutines: LiveData<List<WorkoutRoutine>>

    init {
        // Get the DAO
        val workoutRoutineDAO = AppDatabase.getDatabase(application, viewModelScope).workoutRoutineDAO()
        // Build a new data repository for workout routines
        workoutRoutinesRepository = WorkoutRoutineRepository(workoutRoutineDAO)
        // Get a live view of the workout routines database
        allWorkoutRoutines = workoutRoutinesRepository.allRoutines
    }

    fun insert(workoutRoutine: WorkoutRoutine) = viewModelScope.launch(Dispatchers.IO) {
        workoutRoutinesRepository.insert(workoutRoutine)
    }

    fun update(workoutRoutine: WorkoutRoutine) = viewModelScope.launch(Dispatchers.IO) {
        workoutRoutinesRepository.update(workoutRoutine)
    }

    fun delete(workoutRoutine: WorkoutRoutine) = viewModelScope.launch(Dispatchers.IO) {
        workoutRoutinesRepository.delete(workoutRoutine)
    }
}

I expect the user to be able to move the item n spaces, then drop, and have the update to Room database execute when the user drops the item, but if I put the Room update in the onMove method, the user can only move the item once.

I'm trying to understand the right way to update the Room data when the order of objects change in the recycler view. I'm trying to get the order of those objects to persist even when the user exits the app or changes activities or whatever. How am I supposed to echo those changes back to the Room database, using LiveData?

1 Answers

You can follow this guide on Udacity. It is free, made by Google and uses Kotlin.

Related