ルームDBには3つのエンティティがあります。
Album
、PhotosMediaItem
およびVideosMediaItem
。
VideosMediaItem
とPhotosMediaItem
はMediaItem
から継承します。
MediaItem
はDB内のエンティティではなく、単なる抽象基本クラスです。
特定のアルバム内のすべての写真とビデオのメディアアイテムを、作成日に基づいて降順で返すクエリを作成したいと思います。
したがって、クエリはMediaItemのリストを作成しますが、派生型を使用します。 (PhotoMediaItem
またはVideoMediaItem
)多形的に。
これが私が試したことです:
@Query("SELECT * FROM PhotosMediaItem WHERE PhotosMediaItem = :albumId " +
"UNION SELECT * FROM VideosMediaItem WHERE VideosMediaItem = :albumId" +
" ORDER by CreationDate DESC")
List<MediaItem> getAllMediaInAlbum(int albumId);
MediaItemオブジェクトを開始しようとするため、これは明らかに機能しません。これは私の意図ではありません。このクエリで派生クラスPhotoMediaItem
またはVideoMediaItem
を開始したい
通常のSQLiteHelperを使用して、Roomに移行する前のクエリは次のようになり、正常に機能しました。
public ArrayList<MediaItem> getMediaListByAlbumId(int palbumId)
{
Cursor cursor = null;
try{
ArrayList<MediaItem> mediaList = new ArrayList<>();
String selectQuery = "SELECT "+ mPhotoId +","+ mPhotoCreationDate +", 0 AS mediaType, '' FROM "+ mPhotosTableName + " WHERE " + this.mPhotoAlbumId + "="+palbumId +
" UNION " +
"SELECT "+ mVideoId +","+ mVideoCreationDate + " ,1 AS mediaType, " + mVideoLength + " FROM " + mVideosTableName + " WHERE " + this.mVideoAlbumId +"="+palbumId +
" ORDER BY CreationDate DESC";
cursor = mDB.rawQuery(selectQuery, null);
// looping through all rows and adding to list
if (cursor.moveToFirst()){
do {
// MediaHolder consists of the media ID and its type
int mediaType = cursor.getInt(2);
MediaItem mediaItem = null;
if (mediaType == 0) {
mediaItem = new PhotoMediaItem(cursor.getInt(0), null, palbumId);
} else if (mediaType == 1) {
mediaItem = new VideoMediaItem(cursor.getInt(0), null, palbumId, cursor.getLong(3));
}
mediaList.add(mediaItem);
}
while (cursor.moveToNext());
}
return mediaList;
}
finally {
if(cursor != null){
cursor.close();
}
}
}
では、Roomを使用して同じ効果を実現するにはどうすればよいですか?
ここにはいくつかのオプションがあると思います。
オプション1
単一のテーブルを使用してすべてのMediaItem
を格納し、識別子列を使用してビデオと写真を区別します。クエリを実行し、order by
を適用し、Cursor
を返す単一のDAOメソッドがあります。次に、既存のカーソル操作ロジックを使用してList<MediaItem>
を返すことができます。次のようになります。
@Dao
public abstract class MediaItemDao() {
@Query("you query here")
protected Cursor getByAlbumIdInternal(int albumId);
public List<MediaItem> getByAbumId(int albumId) {
Cursor cursor = null;
try{
List<MediaItem> mediaList = new ArrayList<>();
cursor = getByAlbumIdInternal(albumId);
// looping through all rows and adding to list
if (cursor.moveToFirst()){
do {
// use the discriminator value here
int mediaType = cursor.getInt(cursor.getColumnIndex("you discriminator column name here"));
MediaItem mediaItem = null;
if (mediaType == 0) {
mediaItem = new PhotoMediaItem(cursor.getInt(0), null, palbumId);
} else if (mediaType == 1) {
mediaItem = new VideoMediaItem(cursor.getInt(0), null, palbumId, cursor.getLong(3));
}
mediaList.add(mediaItem);
} while (cursor.moveToNext());
}
return mediaList;
}
finally {
if(cursor != null){
cursor.close();
}
}
}
}
オプション2
VideosMediaItem
とPhotosMediaItem
を格納するために2つの異なるテーブルを使用します。クエリを実行するための2つの内部メソッドと2つの結果セットをマージしてJavaコード)で並べ替えを適用する単一のパブリックメソッドを持つMediaItemDao
があります。このような:
@Dao
public abstract class MediaItemDao() {
@Query("your query to get the videos, no order by")
protected List<VideoMediaItem> getVideosByAlbumId(int albumId);
@Query("your query to get the photos, no order by")
protected List<PhotosMediaItem> getPhotosByAlbumId(int albumId);
@Transaction
public List<MediaItem> getByAlbumId(int albumId) {
final List<MediaItem> mediaItems = new LinkedList<>();
mediaItems.add(getVideosByAlbumId(albumId));
mediaItems.add(getPhotosByAlbumId(albumId));
Collections.sort(mediaItems, <you can add a comparator here>);
return mediaItems;
}
}
編集:このオプションにライブデータを活用する方法は?
前述したように、保護されたメソッドの戻り値の型としてLiveData
を使用して、これらのテーブルの根本的な変更について通知を受け取る必要があります。したがって、次のようになります。
protected LiveData<List<VideoMediaItem>> getVideosByAlbumId(int albumId);
protected LiveData<List<PhotosMediaItem>> getPhotosByAlbumId(int albumId);
単一のLiveData
をクライアントに返すには、これら2つのメソッドの出力を単一のストリームに圧縮する必要があります。これは、カスタムのMediatorLiveData
実装を使用して実現できます。次のようになります。
public class ZipLiveData<T1, T2, R> extends MediatorLiveData<R> {
private T1 mLastLeft;
private T2 mLastRight;
private Zipper<T1, T2, R> mZipper;
public static final <T1, T2, R> LiveData<R> create(@NonNull LiveData<T1> left, @NonNull LiveData<T2> right, Zipper<T1, T2, R> zipper) {
final ZipLiveData<T1, T2, R> liveData = new ZipLiveData(zipper);
liveData.addSource(left, value -> {
liveData.mLastLeft = value;
update();
});
liveData.addSource(right, value -> {
liveData.mLastRight = value;
update();
});
return liveData;
}
private ZipLiveData(@NonNull Zipper<T1, T2, R> zipper) {
mZipper = zipper;
}
private update() {
final R result = zipper.Zip(mLastLeft, mLastRight);
setValue(result);
}
public interface Zipper<T1, T2, R> {
R Zip(T1 left, T2 right);
}
}
次に、次のようにリポジトリのパブリックメソッドで使用します。
public List<MediaItem> getByAlbumId(int albumId) {
return ZipLiveData.create(
getVideosByAlbumId(albumId),
getPhotosByAlbumId(albumId),
(videos, photos) -> {
final List<MediaItem> mediaItems = new LinkedList<>();
mediaItems.add(videos);
mediaItems.add(photos);
Collections.sort(mediaItems, <you can add a comparator here>);
return mediaItems;
}
}
オプション
これは、リポジトリレイヤーが配置されている場合にのみ適用されます。
VideosMediaItem
とPhotosMediaItem
を格納するために2つの異なるテーブルを使用します。それぞれにDAOクラスがあります。両方のDAOに依存し、結果セットを組み合わせて並べ替えを適用するリポジトリがあります。次のようになります。
@Dao
public abstract class VideosMediaItemDao {
@Query("your query to get the videos, no order by")
public List<VideoMediaItem> getByAlbumId(int albumId);
}
@Dao
public abstract class PhotosMediaItemDao {
@Query("your query to get the photos, no order by")
public List<PhotosMediaItem> getByAlbymId(int albumId);
}
public interface MediaItemRepository {
public List<MediaItem> getByAlbumId(int albumId);
}
class MediaItemRepositoryImpl {
private final VideosMediaItemDao mVideoDao;
private final PhotosMediaItemDao mPhotoDao;
MediaItemRepositoryImpl(VideosMediaItemDao videoDao, PhotosMediaItemDao photoDao) {
mVideoDao = videoDao;
mPhotoDao = photoDao;
}
@Override
public List<MediaItem> getByAlbumId(int albumId) {
final List<MediaItem> mediaItems = new LinkedList<>();
mediaItems.add(mVideoDao.getByAlbumId(albumId));
mediaItems.add(mPhotoDao.getByAlbumId(albumId));
Collections.sort(mediaItems, <you can add a comparator here>);
return mediaItems;
}
}
私はそれで少し遊んでいましたが、複数のLiveDataソースを一緒に圧縮するための一般的な解決策を見つけたようです。
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
class ZipLiveData<S>(private val process: (List<S>) -> List<S>) : MediatorLiveData<List<S>>() {
val map = hashMapOf<String, List<S>>()
fun addSource(source: LiveData<*>) {
addSource(source) {
map[source.toString()] = it as List<S>
value = process(map.values.flatten())
}
}
}
そして使用法:
@Transaction
fun findItems(albumId: Int): LiveData<List<MediaItem>> {
val liveData = ZipLiveData<MediaItem> { it.sortedBy { item -> item.weight } }
liveData.addSource(getVideosByAlbumId(albumId))
liveData.addSource(getPhotosByAlbumId(albumId))
liveData.addSource(getSoundsByAlbumId(albumId))
return liveData
}
それが最もエレガントなソリューションかどうかはわかりませんが