【问题标题】:Unable to use results from Observing Room entity无法使用观察室实体的结果
【发布时间】:2020-06-20 11:55:28
【问题描述】:

我的目标

我正在尝试创建一个包含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)

应用数据库

@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 的 Fragment 中,我正在观察 RoutineViewModel 并将例程发送到 RecyclerView 适配器。

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

我的问题

因为ExerciseRoutineJoin 在其表中没有Exercise 名称,所以我正在创建ExerciseRoutineJoins 的MutableMap,其中Routine 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 返回List 而不是LiveData&lt;List&lt;Exercise&gt;&gt;,但这会导致从主线程调用数据库时出现大量问题。

我如何使用观察练习的结果?有没有更聪明的方法可以通过来自不同实体的内容来实现 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)
        }
    }
}

【问题讨论】:

  • 您能具体说明一下您的数据库结构吗?如果您只需要一个 List 在您的表中,您可以创建一个 typeConverter ,这将使事情变得更容易,而无需使用关系。

标签: android kotlin android-recyclerview android-room android-livedata


【解决方案1】:

您可以按照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>

那么你就有了例程和它的练习。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-13
    • 1970-01-01
    • 2020-08-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-12
    相关资源
    最近更新 更多