简体   繁体   English

Android Room:插入具有 ViewModel、Repository、Database 的多对多关系实体

[英]Android Room: Insert a many-to-many relation entity with ViewModel, Repository, Database

I'm creating a simple app to practice working with databases.我正在创建一个简单的应用程序来练习使用数据库。 The app has Playlists and Songs.该应用程序有播放列表和歌曲。 Each Playlist contains many songs, and each Song can be in many Playlists.每个播放列表包含许多歌曲,并且每首歌曲可以在许多播放列表中。 So it will need a many-to-many relation.所以它需要一个多对多的关系。

I'm trying to stick to Android's Activity->ViewModel->Repository->Database architecture using LiveData and Room.我正在尝试使用 LiveData 和 Room 坚持 Android 的 Activity->ViewModel->Repository->Database 架构。

The app's MainActivity gathers two song names and a playlist name from the user, then adds them to the Room database when the button is clicked.应用程序的 MainActivity 从用户那里收集两个歌曲名称和一个播放列表名称,然后在单击按钮时将它们添加到 Room 数据库。

Here's the Playlist object, the Song object, plus an extra object to use as a cross reference, CrossRef:这是播放列表 object、歌曲 object,以及额外的 object 用作交叉引用,CrossRef:

Playlist.class:播放列表.class:

@Entity(tableName="playlist_table")
public class Playlist {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name="playlist_id")
    private int playlistId;
    private String name = "Default Playlist";
}

Song.class:歌曲.class:

@Entity(tableName="song_table")
public class Song {
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name="song_id")
    private int songId;
    private String name;
}

CrossRef.class:交叉参考.class:

@Entity(tableName="cross_ref_table", primaryKeys = {"playlist_id", "song_id"})
public class CrossRef {
    @ColumnInfo(index = true, name = "playlist_id")
    public int playlistId;
    @ColumnInfo(index = true, name = "song_id")
    public int songId;

    public CrossRef(int playlistId, int songId) {
        this.playlistId = playlistId;
        this.songId = songId;
    }
}

MainActivity gets the data from the user, calls MyViewModel to insert the data MainActivity 从用户那里获取数据,调用 MyViewModel 插入数据

MainActivity.class: MainActivity.class:

myViewModel.insert(playlist, songs);

then MyViewModel uses its Repository to:然后 MyViewModel 使用它的 Repository 来:

  1. Insert the playlist to the playlist_table and save it's autogenerated playlistId.将播放列表插入到 playlist_table 并保存它的自动生成的 playlistId。
  2. Insert each song into the song_table saving each songId.将每首歌曲插入到保存每个 songId 的 song_table 中。
  3. Insert a new row in the cross_ref_table.在 cross_ref_table 中插入一个新行。

MyViewModel.class: MyViewModel.class:

public void insert(Playlist playlist, List<Song> newSongs) {
        int playlistId = (int)localRepository.insert(playlist);
        int songId = 0;

        for (Song song: newSongs) {
            if(!songExists(song)) {
                songId = (int)localRepository.insert(song);
            } else {
                songId = song.getSongId();
            }
            CrossRef crossRef = new CrossRef(playlistId, songId);
            localRepository.insert(crossRef);
        }
    }

The Repository then calls the Dao to do the actual work. Repository 然后调用 Dao 来完成实际的工作。 LocalRepository.class:本地存储库.class:

public long insert(Playlist playlist){
    new InsertPlaylistAsyncTask(myDao).execute(playlist);
    return resultId; // Once the async task is done, return the new playlistId.
 }

public long insert(Song song){
    new InsertSongAsyncTask(myDao).execute(song);
    return resultId; // Once the async task is done, return the new songId.
}

public void insert(CrossRef crossRef){
    new InsertCrossRefAsyncTask(myDao).execute(crossRef);
}

MyDao:我的道:

    @Insert                   
    long insert(Playlist playlist);   // returns a long value of the newly inserted playlistId.

    @Insert
    long insert(Song song);           // returns a long value of the newly inserted songId.

    @Insert
    void insert(CrossRef crossRef);

The issue I am running into is getting autogenerated id's.我遇到的问题是获取自动生成的 ID。 They always come back as 0, In MyDao, this line should assign playlistId to the newly inserted playlist ID?他们总是返回为 0,在 MyDao 中,这行应该将 playlistId 分配给新插入的播放列表 ID? right?正确的? int playlistId = (int)localRepository.insert(playlist);

But no, it's always zero.但是不,它总是为零。 Here's the InsertPlaylistAsyncTask in the Repository where the new id SHOULD be passed back onPostExecute:这是 Repository 中的 InsertPlaylistAsyncTask,新 id 应该在 PostExecute 上传回:

    private class InsertPlaylistAsyncTask extends AsyncTask<Playlist, Void, Long> {
        private MyDao myDao;

        private InsertPlaylistAsyncTask(MyDao myDao){
            this.myDao = myDao;
        }

        @Override
        protected Long doInBackground(Playlist... playlists) {

            long id = 0; // TODO: make this an array and return an ArrayList<Long>
            for (Playlist r:playlists) {
                id = myDao.insert(r);
            }
            return id;
        }

        @Override
        protected void onPostExecute(Long playlistId) {
            resultId = playlistId;
        }
    }

If anyone has a good resource to learn more about INSERTing to a database with many-to-many relations, I'm all ears.如果有人有很好的资源来了解更多关于插入到具有多对多关系的数据库的信息,我会全力以赴。 Thanks all.谢谢大家。

I think I found my own solution.我想我找到了自己的解决方案。 Use a handler!使用处理程序!

Create an object PlaylistWithSongs.创建一个 object PlaylistWithSongs。 Call the repository to insertPlaylistWithSongs.调用存储库以 insertPlaylistWithSongs。

Now insert the Playlists and Songs in their tables: The repository inserts the Playlist in a second AsyncTask which calls a handler on the main thread with the playlistId in hand.现在在它们的表中插入播放列表和歌曲:存储库将播放列表插入到第二个 AsyncTask 中,该任务调用主线程上的处理程序,并使用 playlistId。 The repository inserts each song in the list in more AsyncTasks, calling the same handler with each new songId generated.存储库将列表中的每首歌曲插入到更多 AsyncTasks 中,并使用生成的每个新 songId 调用相同的处理程序。

The handler is on the main thread, waiting for ids to roll in. Every time it sees a valid playlistId and a valid songId, it inserts the match into the crossRef table.处理程序在主线程上,等待 ids 进入。每次它看到一个有效的 playlistId 和一个有效的 songId 时,它都会将匹配插入到 crossRef 表中。

PlaylistWithSongs.java:播放列表WithSongs.java:

public class PlaylistWithSongs {
    @Embedded
    public Playlist playlist;

    // The songs variable will contain all songs related by the playlistId and songId.
    @Relation(
            parentColumn = "playlist_id",
            entityColumn = "song_id",
            associateBy = @Junction(CrossRef.class)
    )
    public List<Song> songs;
}

LocalRepository.java: LocalRepository.java:

public void insertPlaylistWithSongs(PlaylistWithSongs playlistWithSongs) {
        MyDao myDao;
        insertPlaylist(playlistWithSongs.getPlaylist());

        for (Song song : playlistWithSongs.getSongs()) {
            insertSong(song);
        }
        tempPlaylistId = 0;
        tempSongId = 0;
}

insertPlaylist inserts the playlist in the table and saves the new id id = (int)myDao.insert(playlist); insertPlaylist 在表中插入播放列表并保存新的 id id = (int)myDao.insert(playlist); . . It packages a message containing the new id ids.putInt(PLAYLISTID, id);它打包一条包含新 id 的消息ids.putInt(PLAYLISTID, id); to send to the handler on the main thread handler.sendMessage(msg);发送到主线程上的处理程序handler.sendMessage(msg); :

public void insertPlaylist(Playlist playlist){
        new InsertPlaylistAsyncTask(myDao).execute(playlist);
    }
    private class InsertPlaylistAsyncTask extends AsyncTask<Playlist, Void, Void> {
        private MyDao myDao;

        private InsertPlaylistAsyncTask(MyDao myDao){
            this.myDao = myDao;
        }

        @Override        
        protected Void doInBackground(Playlist... playlists) {
            int id = 0;
            for (Playlist playlist:playlists) {

                Playlist retrievedFromDatabase = myDao.getPlaylistByName(playlist.getName());

                if(retrievedFromDatabase == null){
                    // If playlist name doesn't exist
                    id = (int)myDao.insert(playlist);
                } else {
                    // Else set the id to the existing playlist id
                    id = retrievedFromDatabase.getPlaylistId();
                }

                // Pass the new id to the main thread once the background insert is complete.
                Bundle ids = new Bundle();
                ids.putInt(PLAYLISTID, id);
                Message msg = new Message();
                msg.setData(ids);
                msg.setTarget(handler);
                handler.sendMessage(msg);
            }

            return null;
        }
}

Each song is handled the exact same way, so I won't repeat the code.每首歌曲的处理方式完全相同,所以我不会重复代码。

Each time the handler receives a new id, it updates the Repository's static variables, private static int tempSongId = 0;每次处理程序收到一个新 id 时,它都会更新存储库的 static 变量, private static int tempSongId = 0; and private static int tempPlaylistId = 0;private static int tempPlaylistId = 0; , then checks if it has enough valid information to insert the crossRef: ,然后检查它是否有足够的有效信息来插入 crossRef:

Handler handler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {

            Bundle ids = msg.getData();
            if(ids.getInt(PLAYLISTID) != 0){
                tempPlaylistId = msg.getData().getInt(PLAYLISTID);
            }
            if(ids.getInt(SONGID) != 0){
                tempSongId = msg.getData().getInt(SONGID);
            }

            if(tempPlaylistId!=0 && tempSongId!=0) {
                // if both ids were retrieved
                CrossRef crossRef = new CrossRef(tempPlaylistId, tempSongId);
                Log.d(TAG, "handler insert(crossRef): " + tempPlaylistId + "," + tempSongId);
                insertCrossRef(crossRef);

            }else{
                // if not, do nothing. We need both ids to insert a crossRef entry.
            }
        }
};

One issue with this code: If the main activity stops before the AsyncTasks are complete, a memory leak could occur.此代码的一个问题:如果主要活动在 AsyncTask 完成之前停止,则可能会发生 memory 泄漏。 Perhaps adding a weak reference could fix that problem.也许添加一个弱引用可以解决这个问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM