Невозможно использовать результаты объекта "Комната наблюдения" - PullRequest
0 голосов
/ 20 июня 2020

Моя цель

Я пытаюсь создать 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)
        }
    }
}

1 Ответ

1 голос
/ 20 июня 2020

Вы можете запрашивать подпрограммы с упражнениями из базы данных, как описано здесь :

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>

Тогда у вас есть оба Процедура и упражнения.

...