はじめに
リポジトリとはなんぞやとや言うことで有名OSSメディアプレイヤーアプリである「VLC for Android」にあるリポジトリの実装を調べる。
当方はAndroidの素人なのでその辺はご了承を。
各種リンク
アプリ内で定義しているリポジトリ
クラス | 概要 |
---|---|
BrowserFavRepository | お気に入りを保持? |
DirectoryRepository | ディレクトリを保持? |
ExternalSubRepository | 外部ストレージ |
MediaMetadataRepository | メタデータ |
MediaPersonRepository | ? |
MoviepediaApiRepository | MoviepediaのApi |
OpenSubtitleRepository | ? |
PersonRepository | MoviePediaから取得した人をキャッシュ |
SlaveRepository | ? |
データソース
どこからデータを持ってきているかを調べた結果。データベースとAPI、オンメモリーの3つのパターンがある
クラス | データソース |
---|---|
BrowserFavRepository | データベース(BrowserFavDao) |
DirectoryRepository | データベース(CustomDirectoryDao) |
ExternalSubRepository | データベース(ExternalSubDao)、 オンメモリー |
MediaMetadataRepository | データベース(MediaMetadataDataFullDao、MediaMetadataDao、MediaImageDao) |
MediaPersonRepository | データベース(MediaPersonJoinDao) |
MoviepediaApiRepository | REST API(IMoviepediaApiService) |
OpenSubtitleRepository | REST API?(IOpenSubtitleService) |
PersonRepository | データベース(PersonDao) |
SlaveRepository | データベース(SlaveDao) |
ユニットテストを入れているか
クラス | テストクラス |
---|---|
BrowserFavRepository | ○(BrowserFavRepositoryTest) |
DirectoryRepository | ○(DirectoryRepositoryTest) |
ExternalSubRepository | ○(ExternalSubRepositoryTest) |
MediaMetadataRepository | × |
MediaPersonRepository | × |
MoviepediaApiRepository | × |
OpenSubtitleRepository | ○?(SubtitlesModelTest) |
PersonRepository | × |
SlaveRepository | ○(SlaveRepositoryTest) |
データ取得元がデータベースの場合のクラス構成
↓はExternalSubRepository周りのクラス構成。ExternalSubクラスはデータベース、SubtitleItemはオンメモリーで管理している。
LiveData、Coroutineが絡んでいるがここでは割愛する。
ExternalSubRepositoryの責務はなんなのかというと、↓のコードをにある、Roomでは表現できない?制約にそってCRUD操作を実装している。後はテスタビリティの確保。
fun addDownloadingItem(key: Long, item: SubtitleItem) {
_downloadingSubtitles.add(key, item.copy(state = State.Downloading))
}
fun getDownloadedSubtitles(mediaUri: Uri): LiveData<List<org.videolan.vlc.mediadb.models.ExternalSub>> {
val externalSubs = externalSubDao.get(mediaUri.path!!)
return Transformations.map(externalSubs) { list ->
val existExternalSubs: MutableList<org.videolan.vlc.mediadb.models.ExternalSub> = mutableListOf()
list.forEach {
if (File(Uri.decode(it.subtitlePath)).exists())
existExternalSubs.add(it)
else
deleteSubtitle(it.mediaPath, it.idSubtitle)
}
existExternalSubs
}
}
データ取得元がREST APIの場合のクラス構成
↓はOpenSubtitleRepository周りのクラス構成。
OpenSubtitleRepositoryの責務はなんなのかというと、↓のコードにある通りRest APIのクエリーパラメータの組み立てと補正。
クエリーオブジェクト的な責務を行っている状態。後はテスタビリティの確保。
suspend fun queryWithImdbid(imdbId: Int, tag: String?, episode: Int? , season: Int?, languageId: String? ): List<OpenSubtitle> {
val actualEpisode = episode ?: 0
val actualSeason = season ?: 0
val actualLanguageId = languageId ?: ""
val actualTag = tag ?: ""
return openSubtitleService.query(
imdbId = String.format("%07d", imdbId),
tag = actualTag,
episode = actualEpisode,
season = actualSeason,
languageId = actualLanguageId)
}
ExternalSubRepositoryのユニットテスト
案の定、ExternalSubDaoをモック化してテストしている。
OpenSubtitleRepositoryのユニットテスト
SubtitlesModelTestで行っている。SubtitlesModelはViewModel派生のクラスで色々面白い事をしているがここでは触れない。
OpenSubtitleRepositoryそのもの、ExternalSubDaoをモック化してテストしている。 RetroFitのクラスはモック化していなかった。
その他リポジトリの(テスタビリティ担保以外の)責務
クラス | (テスタビリティ担保以外の)責務 |
---|---|
BrowserFavRepository | サーバー保存のお気に入りとローカル保存のお気に入りの整合性をとっている? |
DirectoryRepository | 実際にフォルダを作成、削除して、DBに同期している? |
ExternalSubRepository | 説明済 |
MediaMetadataRepository | クエリーの組み立て |
MediaPersonRepository | なし |
MoviepediaApiRepository | クエリーの組み立て |
OpenSubtitleRepository | 説明済 |
PersonRepository | なし |
SlaveRepository | 様々な保存パターンに対応する。SQL例外の補正 |
まとめ
- テスタビリティ担保以外の主な責務はクエリーの組み立て、サーバーとローカルキャッシュの整合性、SQL周りの隠蔽、Retrofit周りの隠蔽
- Coroutine、LiveDataと密結合している。AndroidのUIでしか使わないから?
- シングルトンについてSingletonHolderとCompanionオブジェクトを分けているのはなに?