How to add insert and update operations to a m:n relationship in Room
I have to do some andoid development (in Java) for school and that’s why I play around with Room.
My goal is to provide a very simple user interface that allows users to add participants to events. An event has exactly one owner (who is a person) and several participants (who are also person’s). Each person can take part in several events.
I have to use LiveData and ViewModel.
Entities:
@Entity(tableName = "events") public class Event { public Event(Person owner, String name, Instant date) { this.name = name; this.owner = owner; this.date = date; } @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "event_id") private long id; private Instant date; private String name; @Embedded(prefix = "owner_") private final Person owner; // Getters and setters ...
@Entity(tableName = "persons") public class Person { @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "person_id") private long id; private String name; // Getters and setters ...
For the m:n relationship mapping i use this entity:
@Entity(primaryKeys = {"event_id", "person_id"}) public class EventToPerson { @ColumnInfo(name = "event_id") public long eventId; @ColumnInfo(name = "person_id") public long personId; }
And this class to access it:
public class EventWithPersons { @Embedded public Event event; @Relation( parentColumn = "event_id", entityColumn = "person_id", associateBy = @Junction(EventToPerson.class) ) public List<Person> participants; public EventWithPersons() { } public EventWithPersons(Event event, Person participant) { this.event = event; this.participants = new ArrayList<>(); this.participants.add(participant); } }
The DAO:
@Dao public interface EventDao { @Insert() void insert(Event event); @Query("DELETE FROM events") void deleteAll(); @Delete void deleteDbEvent(Event event); @Update public void updateDbEvent(Event event); @Query("SELECT * from events") LiveData<List<Event>> getAllEvents(); @Transaction @Query("SELECT * FROM events") LiveData<List<EventWithPersons>> getEventsWithPersons(); @Transaction @Update public void updateEventToPerson(EventToPerson eventToPerson); @Transaction @Delete public void deleteEventToPerson(EventToPerson eventToPerson); @Transaction @Insert void insert(EventToPerson eventToPerson); }
The Repository:
public class EventRepo { private EventDao mEventDao; private LiveData<List<EventWithPersons>> allEvents; public EventRepo(Application application) { AppDatabase db = AppDatabase.getDatabase(application); mEventDao = db.eventDao(); allEvents = mEventDao.getEventsWithPersons(); } public LiveData<List<EventWithPersons>> getAllEvents() { return allEvents; } public void insert(Event event) { AppDatabase.databaseWriteExecutor.execute(() -> mEventDao.insert(event)); } public void insert(EventWithPersons event) { event.participants.forEach(person -> { EventToPerson eventToPerson = new EventToPerson(); eventToPerson.eventId = event.event.getId(); eventToPerson.personId = person.getId(); AppDatabase.databaseWriteExecutor.execute(() -> mEventDao.insert(eventToPerson)); }); } }
The ViewModel:
public class EventViewModel extends AndroidViewModel { private EventRepo mRepository; private LiveData<List<EventWithPersons>> mAllEvents; public EventViewModel(Application application) { super(application); mRepository = new EventRepo(application); mAllEvents = mRepository.getAllEvents(); } public LiveData<List<EventWithPersons>> getAllEvents() { return mAllEvents; } public void insert(Event event) { mRepository.insert(event); } public void insert(EventWithPersons event) { mRepository.insert(event); } public void update(EventWithPersons eventWithPersons) { mRepository.insert(eventWithPersons); } }
And finally my MainActicity which provides some basic view elements for testing:
public class MainActivity extends AppCompatActivity { private EventViewModel eventViewModel; TextView textView; Button button; EditText editText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.show_participants_text_view); button = findViewById(R.id.button); editText = findViewById(R.id.editText); Person owner = new Person("OWNER"); Person participant1 = new Person("participant1"); Event event = new Event(owner, "Party", Instant.now()); button.setOnClickListener(v -> { Log.d(this.getLocalClassName(), "Button pressed."); Person p = new Person(editText.getText().toString()); eventViewModel.update(new EventWithPersons(event, p)); }); eventViewModel = new ViewModelProvider(this).get(EventViewModel.class); eventViewModel.getAllEvents().observe(this, events -> { setParticipants(events); Log.d(this.getLocalClassName(), "number of events: " + eventViewModel.getAllEvents().getValue().size()); }); eventViewModel.insert(new EventWithPersons(event, participant1)); } private void setParticipants(List<EventWithPersons> events) { Log.d(this.getLocalClassName(), "Set events."); String participants = events.get(0).participants.stream().map(Person::getName).collect(Collectors.joining(", ")); textView.setText(participants); } }
With this classes i alway run into this erros:
android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: EventToPerson.event_id, EventToPerson.person_id (code 1555)
I think something is wrong with the DAO but I can’t find out. Can you help me set this up properly?
I used this tutorial: https://codelabs.developers.google.com/codelabs/android-room-with-a-view/#0
You can find the whole project here:
https://github.com/jriegraf/room-test