VLC for Androidのリポジトリについて


はじめに

リポジトリとはなんぞやとや言うことで有名OSSメディアプレイヤーアプリである「VLC for Android」にあるリポジトリの実装を調べる。
当方はAndroidの素人なのでその辺はご了承を。

各種リンク

アプリ
GitHub
公式サイト

アプリ内で定義しているリポジトリ

クラス 概要
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オブジェクトを分けているのはなに?