I've defined a model that represents a meeting, with a menu and a workout plan. The menu has a list of courses, each of which has a list of meals, and the workout plan has a list of exercises.
[
{
"menu": {
"courses": [
{
"meals": [
{
...
}
],
}
],
},
"workoutPlan": {
"exercises": [
{
...
},
]
},
}
]
in that way:
PopulatedMeeting.kt
data class PopulatedMeeting(
@Embedded val meeting: MeetingEntity,
@Relation(
parentColumn = "menuId",
entityColumn = "id",
entity = MenuEntity::class
)
val menu: PopulatedMenu,
@Relation(
parentColumn = "workoutPlanId",
entityColumn = "id",
entity = WorkoutPlanEntity::class
)
val workoutPlan: PopulatedWorkoutPlan
)
PopulatedMenu.kt
data class PopulatedMenu(
@Embedded
val menu: MenuEntity,
@Relation(
parentColumn = "id",
entityColumn = "id",
associateBy = Junction(
value = MenuCourseCrossRef::class,
parentColumn = "menu_id",
entityColumn = "course_id"
),
entity = CourseEntity::class
)
val courses: List<PopulatedCourse>
)
When I run the app, I'm getting this execption:
java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter menu
The reason is most likely that you have a Meeting that does not reference a Menu.
Consider the following data which results in:-
Caused by: java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter menu
at a.a.so74866469kotlinroomrelations.PopulatedMeeting.<init>(Unknown Source:7)
at a.a.so74866469kotlinroomrelations.AllDao_Impl.getAllPopulatedMeetings(AllDao_Impl.java:382)
at a.a.so74866469kotlinroomrelations.MainActivity.onCreate(MainActivity.kt:34)
The database, via App inspection has:-
The MeetingEntity table populated with:-
The MenuEntity table populated with:-
Hence the menu will be null when retrieving a PopulatedMeeting.
The following activity code was used to create the above:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getALlDao()
val pm = dao.getAllPopulatedMeetings()
val c1 = dao.insert(CourseEntity(courseName = "C1"))
val c2 = dao.insert(CourseEntity(courseName = "C2"))
val c3 = dao.insert(CourseEntity(courseName = "C3"))
val w1 = dao.insert(WorkoutPlanEntity(workoutPlanName = "W1"))
val w2 = dao.insert(WorkoutPlanEntity(workoutPlanName = "W2"))
val w3 = dao.insert(WorkoutPlanEntity(workoutPlanName = "W3"))
val m1 = dao.insert(MenuEntity( workoutPlanId = w1, menuName = "M1"))
val m2 = dao.insert(MenuEntity(workoutPlanId = w2, menuName = "M2"))
val m3 = dao.insert(MenuEntity(workoutPlanId = w3, menuName = "M3"))
dao.insert(MenuCourseCrossRef(menu_id = m1, course_id = c1))
dao.insert(MenuCourseCrossRef(menu_id = m1, course_id = c2))
dao.insert(MenuCourseCrossRef(menu_id = m2, course_id = c2))
dao.insert(MenuCourseCrossRef(menu_id = m2, course_id = c3))
dao.insert(MenuCourseCrossRef(menu_id = m3, course_id = c3))
val meet1 = dao.insert(MeetingEntity(menuId = m1, meetingName = "MEET1"))
val meet2 = dao.insert(MeetingEntity(menuId = m2, meetingName = "MEET2"))
logPopulatedMeetings(dao.getAllPopulatedMeetings(),"STG1")
val meet3 = dao.insert(MeetingEntity(menuId = 100, meetingName = "MEET3"))
logPopulatedMeetings(dao.getAllPopulatedMeetings(),"STG2")
}
fun logPopulatedMeetings(populatedMeetingsList: List<PopulatedMeeting>, suffix: String) {
val TAG = "DBINFO_$suffix"
val sb = StringBuilder()
for (pm in populatedMeetingsList) {
sb.clear()
for (c in pm.menu.courses) {
sb.append("\n\t${c.courseName}")
}
Log.d(TAG,"Meeting is ${pm.meeting.meetingName} Menu is ${pm.menu.menu.menuName} it has ${pm.menu.courses.size} courses. They are:-$sb")
}
}
}
The log when running the above includes:-
2022-12-21 10:37:37.520 D/DBINFO_STG1: Meeting is MEET1 Menu is M1 it has 2 courses. They are:-
C1
C2
2022-12-21 10:37:37.520 D/DBINFO_STG1: Meeting is MEET2 Menu is M2 it has 2 courses. They are:-
C2
C3
2022-12-21 10:37:37.530 D/AndroidRuntime: Shutting down VM
2022-12-21 10:37:37.534 E/AndroidRuntime: FATAL EXCEPTION: main
Process: a.a.so74866469kotlinroomrelations, PID: 19356
java.lang.RuntimeException: Unable to start activity ComponentInfo{a.a.so74866469kotlinroomrelations/a.a.so74866469kotlinroomrelations.MainActivity}: java.lang.NullPointerException: Parameter specified as non-null
i.e. the PopulatedMeetings with a valid reference to a Menu are fine and utilise your PopulatedMeeting and PopulatedMenu (albeit it that the related Workoutplan was excluded for convenience/brevity).
You may wish to consider enforcing Referential Integrity (e.g. so that the menu_id cannot be a value that does not reference an actual menu).
To enforce referential integrity you can setup Foreign Keys e.g. if the following were coded:-
@Entity(
foreignKeys = [
ForeignKey(
MenuEntity::class,
parentColumns = ["id"],
childColumns = ["menuId"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
]
)
data class MeetingEntity(
@PrimaryKey
val id: Long?=null,
val menuId: Long,
val meetingName: String
)
Then the code above would instead fail with the following in the log (and more importantly when trying to insert the errant reference):-
2022-12-21 10:48:08.427 D/DBINFO_STG1: Meeting is MEET1 Menu is M1 it has 2 courses. They are:-
C1
C2
2022-12-21 10:48:08.427 D/DBINFO_STG1: Meeting is MEET2 Menu is M2 it has 2 courses. They are:-
C2
C3
2022-12-21 10:48:08.430 D/AndroidRuntime: Shutting down VM
2022-12-21 10:48:08.433 E/AndroidRuntime: FATAL EXCEPTION: main
Process: a.a.so74866469kotlinroomrelations, PID: 19822
java.lang.RuntimeException: Unable to start activity ComponentInfo{a.a.so74866469kotlinroomrelations/a.a.so74866469kotlinroomrelations.MainActivity}: android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3449)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Caused by: android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY)
at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:938)
at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:790)
at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:88)
at androidx.sqlite.db.framework.FrameworkSQLiteStatement.executeInsert(FrameworkSQLiteStatement.kt:42)
at androidx.room.EntityInsertionAdapter.insertAndReturnId(EntityInsertionAdapter.kt:102)
at a.a.so74866469kotlinroomrelations.AllDao_Impl.insert(AllDao_Impl.java:139)
at a.a.so74866469kotlinroomrelations.MainActivity.onCreate(MainActivity.kt:38)
:-
fun insertIgnoringFKConflict(meetingEntity: MeetingEntity): Long {
var rv = -1L;
try {
rv = insert(meetingEntity)
} catch (e: SQLiteConstraintException) {
rv = -1
}
finally {
return rv
}
}
In which case replacing the insert with insertIgnoringFKConflict for the 3 Meetings results in no failure and the log including:-
2022-12-21 10:59:12.898 D/DBINFO_STG1: Meeting is MEET1 Menu is M1 it has 2 courses. They are:-
C1
C2
2022-12-21 10:59:12.898 D/DBINFO_STG1: Meeting is MEET2 Menu is M2 it has 2 courses. They are:-
C2
C3
2022-12-21 10:59:12.904 D/DBINFO_STG2: Meeting is MEET1 Menu is M1 it has 2 courses. They are:-
C1
C2
2022-12-21 10:59:12.904 D/DBINFO_STG2: Meeting is MEET2 Menu is M2 it has 2 courses. They are:-
C2
C3