Reputation: 117
My Goal
I am trying to make a RecyclerView that contains a list of Routine
entities. I have created a many to many relationship between Routine
entities and Exercise
entities. My RecyclerView needs to contain the name of the Routine
and a string of the Exercise
names associated with a 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
}
}
}
}
In the Fragment that contains this RecyclerView, I am observing the RoutineViewModel
and sending the Routines to the RecyclerView adapter.
private fun sendRoutinesToAdapter(adapter: RoutineAdapter) {
routineViewModel.allRoutines.observe(viewLifecycleOwner, Observer { routines ->
routines?.let { adapter.setRoutines(it) }
})
}
My Problem
Because the ExerciseRoutineJoin
does not have the Exercise
name in its table, I am creating a MutableMap of the ExerciseRoutineJoins with the Routine ID as the key and a string of all the Exercise names associated with that ID as the value.
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()
}
My Observer does not seem to be providing an Exercise name to my StringBuilder. I have tried having the ExerciseViewModel
return a List
instead of LiveData<List<Exercise>>
but that cause a ton of problems with calling the database from the main thread.
How can I use the results from observing the Exercises? Is there a smarter way to achieve this goal of the RecyclerView with content from different entities?
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)
}
}
}
Upvotes: 0
Views: 58
Reputation: 1252
You can query routines with exercises from the database as explained here:
data class RoutineWithExercises(
@Embedded val routine: Routine,
@Relation(
parentColumn = "routineId",
entityColumn = "exerciseId",
associateBy = @Junction(ExerciseRoutineJoin::class)
)
val exercices: List<Exercise>
)
And then in your RoutineDao:
@Transaction
@Query("SELECT * FROM Routine")
fun getRoutinesWithExercises(): List<RoutineWithExercises>
Then you have both the Routine and it's Exercises.
Upvotes: 1