Android 階層型アーキテクチャ　データレイヤについて

# はじめに
Android Developersで紹介されている階層型アーキテクチャの中にはデータレイヤという層が存在するので、簡単にまとめてみる。

# データレイヤの構造
データレイヤにはアプリに必要なデータとビジネスロジックが含まれる。UI関連の状態やUIロジックに関してはUIレイヤの責任範囲になる。
また、データレイヤはRepositoriesとDataSourcesによって構成される。

## Repositories

命名規則は

データの種類 + Repository


例：NewsRepositoryなど

### リポジトリの持つ役割

- アプリの他の部分にデータを公開する。
- データの変更を一元管理する。
- 複数のデータソース間の競合を解決する。
- アプリの他の部分からデータソースを抽象化する。
- ビジネス ロジックを格納する。


### リポジトリはコンストラクタでデータソースを受け取る

```kotlin
class ContentRepository(
    private val contentRemoteDataSource: ContentRemoteDataSource
) { /* ... */ }
```

### データソースはDI時に生成してRepositryへ渡す。

```kotlin
@Module
@InstallIn(SingletonComponent::class)
object ContentModule {
    @Provides
    @Singleton
    fun provideContentRepository(@ApplicationContext context: Context): ContentRepository {
        return ContentRepository(
            createContentRemoteDatasource(context)
        )
    }
}
```

DIを使うことで例えばViewModelの単体テストをやる場合データソースをテスト用に差し替えてテストするようなことがやりやすくなる。

## DataSources

命名規則は

データの種類 + ソースの種類 + DataSource


例：NewsRemoteDataSourcesなど

ソースの種類に関してはRemoteやLocal、NetworkやDiskがおすすめ。SharedPreferenceなど実装の詳細に基づく名前は付けない。

#### データソースとしてよく使いそうなもの
- ネットワーク（例：Kotr, Retrofit）
- ローカルデータベース（例：Room）
- ローカル設定値（例：SharedPreference, DataStore）
- バックグラウンドタスク（例：WrokMangaer）

これらを○○DataSourceクラスから呼び出して使う。
Repositoryから直接呼び出さないのは、そうすることで差し替えが容易になりテストしやすい。SharedPreferenceからDataStoreに移行するようなことも楽になる。

## キャッシュ

フェッチしたデータをRepositoryでキャッシュする方法も紹介されていた。
あとはOKHttpでキャッシュする方法もある

```kotlin
// 参考：https://developer.android.com/topic/architecture/data-layer?hl=ja
class NewsRepository(
    private val newsRemoteDataSource: NewsRemoteDataSource
) {
    // Mutex to make writes to cached values thread-safe.
    private val latestNewsMutex = Mutex()

    // Cache of the latest news got from the network.
    private var latestNews: List<ArticleHeadline> = emptyList()

    suspend fun getLatestNews(refresh: Boolean = false): List<ArticleHeadline> {
        if (refresh || latestNews.isEmpty()) {
        val networkResult = newsRemoteDataSource.fetchLatestNews(
            // Thread-safe write to latestNews
            latestNewsMutex.withLock {
                this.latestNews = networkResult
            }
        }

        return latestNewsMutex.withLock { this.latestNews }
    }
}
```

## データの分離
Apiで取得したデータから必要なデータだけを取り出すことでアプリのメモリを節約できる。
また、アプリ側で使いやすいデータ型への変換などもデータレイヤで行う。

```Kotlin
data class DataApiModel(
    val id: Long,
    val name: String,
    val created: Date,
    val lastUpdated: Date,
    val authorId: Int
)
```

```Kotlin
data class DataModel(
    val id: Long,
    val name: String,
    val created: Date
)
```

## WorkerMangaer

WorkMangaerはDataSourceクラスでラップすることで、データソースとして扱う。

```Kotlin
// 参考：https://developer.android.com/topic/architecture/data-layer?hl=ja

private const val REFRESH_RATE_HOURS = 4L
private const val FETCH_LATEST_NEWS_TASK = "FetchLatestNewsTask"
private const val TAG_FETCH_LATEST_NEWS = "FetchLatestNewsTaskTag"

class NewsTasksDataSource(
    private val workManager: WorkManager
) {
    fun fetchNewsPeriodically() {
        val fetchNewsRequest = PeriodicWorkRequestBuilder<RefreshLatestNewsWorker>(
            REFRESH_RATE_HOURS, TimeUnit.HOURS
        ).setConstraints(
            Constraints.Builder()
                .setRequiredNetworkType(NetworkType.TEMPORARILY_UNMETERED)
                .setRequiresCharging(true)
                .build()
        )
            .addTag(TAG_FETCH_LATEST_NEWS)

        workManager.enqueueUniquePeriodicWork(
            FETCH_LATEST_NEWS_TASK,
            ExistingPeriodicWorkPolicy.KEEP,
            fetchNewsRequest.build()
        )
    }

    fun cancelFetchingNewsPeriodically() {
        workManager.cancelAllWorkByTag(TAG_FETCH_LATEST_NEWS)
    }
}
```