Моя цель
Я пытаюсь создать RecyclerView, который содержит список Routine
сущностей. Я создал отношения "многие ко многим" между Routine
сущностями и Exercise
сущностями. Мой RecyclerView должен содержать имя Routine
и строку Exercise
имен, связанных с Routine
.
Exercise
@Entity
data class Exercise(
@PrimaryKey(autoGenerate = true)
val exerciseId: Int,
val exerciseName: String
)
Routine
@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
не имеет имени Exercise
в своей таблице, я создаю MutableMap для ExerciseRoutineJoins с идентификатором процедуры в качестве ключа и строкой всех имен упражнений, связанных с этим идентификатором в качестве значения.
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<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)
}
}
}