无法使用观察室实体的结果

Unable to use results from Observing Room entity

我的目标

我正在尝试创建一个包含 Routine 实体列表的 RecyclerView。我在 Routine 实体和 Exercise 实体之间创建了多对多关系。我的 RecyclerView 需要包含 Routine 的名称以及与 Routine.

关联的 Exercise 名称的字符串

运动

@Entity
data class Exercise(
    @PrimaryKey(autoGenerate = true)
    val exerciseId: Int,
    val exerciseName: String
)

日常

@Entity
data class Routine(
    @PrimaryKey(autoGenerate = true)
    val routineId: Int,
    val routineName: String
)

ExerciseRoutineJoin

@Entity(
    primaryKeys = arrayOf("exerciseId", "routineId"),
    foreignKeys = arrayOf(
        ForeignKey(
            entity = Exercise::class,
            parentColumns = arrayOf("exerciseId"),
            childColumns = arrayOf("exerciseId"),
            onDelete = ForeignKey.NO_ACTION
        ),
        ForeignKey(
            entity = Routine::class,
            parentColumns = arrayOf("routineId"),
            childColumns = arrayOf("routineId"),
            onDelete = ForeignKey.NO_ACTION
        )
    )
)
data class ExerciseRoutineJoin(val exerciseId: Int, val routineId: Int)

AppDatabase

@Database(entities = arrayOf(Routine::class, Exercise::class, ExerciseRoutineJoin::class), version = 1, exportSchema = false)
public abstract class AppDatabase : RoomDatabase() {

   abstract fun routineDao(): RoutineDao
   abstract fun exerciseDao(): ExerciseDao
   abstract fun exerciseRoutineJoinDao(): ExerciseRoutineJoinDao

   companion object {
        // Singleton prevents multiple instances of database opening at the
        // same time. 
        @Volatile
        private var INSTANCE: WordRoomDatabase? = null

        fun getDatabase(context: Context): WordRoomDatabase {
            val tempInstance = INSTANCE
            if (tempInstance != null) {
                return tempInstance
            }
            synchronized(this) {
                val instance = Room.databaseBuilder(
                        context.applicationContext,
                        AppDatabase::class.java, 
                        "app_database"
                    ).build()
                INSTANCE = instance
                return instance
            }
        }
   }
}

在包含此 RecyclerView 的片段中,我正在观察 RoutineViewModel 并将例程发送到 RecyclerView 适配器。

private fun sendRoutinesToAdapter(adapter: RoutineAdapter) {
    routineViewModel.allRoutines.observe(viewLifecycleOwner, Observer { routines ->
        routines?.let { adapter.setRoutines(it) }
    })
}

我的问题

因为 ExerciseRoutineJoin 在其 table 中没有 Exercise 名称,我正在创建一个 Mutable 的 ExerciseRoutineJoins 地图,其中例程 ID 作为键和与该 ID 关联的所有练习名称的字符串作为值。

private fun combineExerciseNamesForRoutine(
        routineId: Int,
        joins: List<ExerciseRoutineJoin>
    ): String {

        exerciseViewModel = ViewModelProvider(this).get(ExerciseViewModel::class.java)

        val builder = StringBuilder()

        joins.forEach {
            if (it.routineId == routineId) {
                // TODO Why is builder not appending the string?
                exerciseViewModel.getExerciseById(it.exerciseId)?.observe(viewLifecycleOwner,
                    Observer { exercise -> builder.append(exercise.exerciseName) })
            }
        }
        return builder.toString()
    }

我的观察者似乎没有为我的 StringBuilder 提供练习名称。我试过使用 ExerciseViewModel return List 而不是 LiveData<List<Exercise>> 但这会导致从主线程调用数据库时出现大量问题。

如何使用观察功法的结果?有没有更聪明的方法来实现 RecyclerView 的这个目标与来自不同实体的内容?

StartWorkoutFragment.kt

class StartWorkoutFragment : Fragment() {
    private lateinit var routineViewModel: RoutineViewModel
    private lateinit var exerciseViewModel: ExerciseViewModel
    private lateinit var exerciseRoutineJoinViewModel: ExerciseRoutineJoinViewModel
    private var adapter: RoutineAdapter? = null

    companion object {
        fun newInstance(): StartWorkoutFragment {
            return StartWorkoutFragment()
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_start_workout, container, false)
        val activity = activity as Context

        val recyclerView: RecyclerView = view.findViewById(R.id.rv_routines)
        val adapter = RoutineAdapter(activity)
        recyclerView.layoutManager = GridLayoutManager(activity, 2)
        recyclerView.adapter = adapter

        routineViewModel = ViewModelProvider(this).get(RoutineViewModel::class.java)

        setUpRecyclerViewContent(adapter)
        setUpAddRoutineButton(view)

        return view
    }

    private fun setUpRecyclerViewContent(adapter: RoutineAdapter) {
        sendRoutinesToAdapter(adapter)
        getExerciseRoutineJoinsFromDatabase()
    }

    private fun sendRoutinesToAdapter(adapter: RoutineAdapter) {
        routineViewModel.allRoutines.observe(viewLifecycleOwner, Observer { routines ->
            routines?.let { adapter.setRoutines(it) }
        })
    }

    private fun getExerciseRoutineJoinsFromDatabase() {
        val routineIdWithExercisesAsStringPairs = mutableMapOf<Int, String>() // create empty map

        exerciseRoutineJoinViewModel =
            ViewModelProvider(this).get(ExerciseRoutineJoinViewModel::class.java)
        exerciseRoutineJoinViewModel.allExerciseRoutineJoins.observe(
            viewLifecycleOwner,
            Observer { joins ->
                joins?.let { it ->
                    it.forEach {
                        if (!routineIdWithExercisesAsStringPairs.containsKey(it.routineId))   // if the routine ID hasn't already been mapped, map the routine ID to the exercise String
                            routineIdWithExercisesAsStringPairs[it.routineId] =
                                combineExerciseNamesForRoutine(it.routineId, joins)
                    }
                }
            })
        adapter?.setRoutineIdWithExercisesAsStringPairs(routineIdWithExercisesAsStringPairs)   // send the map to the RecyclerView adapter

    }

    private fun combineExerciseNamesForRoutine(
        routineId: Int,
        joins: List<ExerciseRoutineJoin>
    ): String {

        exerciseViewModel = ViewModelProvider(this).get(ExerciseViewModel::class.java)

        val builder = StringBuilder()

        joins.forEach {
            if (it.routineId == routineId) {
                // TODO Why is builder not appending the string?
                exerciseViewModel.getExerciseById(it.exerciseId)?.observe(viewLifecycleOwner,
                    Observer { exercise -> builder.append(exercise.exerciseName) })
            }
        }
        return builder.toString()
    }

    private fun setUpAddRoutineButton(view: View) {
        val button: Button = view.findViewById(R.id.buttonAddRoutine)
        button.setOnClickListener {
            view.findNavController().navigate(R.id.navigation_add_routine)
        }
    }
}

您可以按照说明从数据库中查询包含练习的例程here:

data class RoutineWithExercises(
    @Embedded val routine: Routine,
    @Relation(
         parentColumn = "routineId",
         entityColumn = "exerciseId",
         associateBy = @Junction(ExerciseRoutineJoin::class)
    )
    val exercices: List<Exercise>
)

然后在你的 RoutineDao 中:

@Transaction
@Query("SELECT * FROM Routine")
fun getRoutinesWithExercises(): List<RoutineWithExercises>

然后你就有了例程和练习。