こんにちは、モバイルクライアントグループの隅山です。 前回は両OS開発についてのブログを書きましたが、今回はDropboxのStoreを用いてAndroidアプリの通信量を削減した話をしていきます。
Storeについて
まず、DropboxのStoreとはアプリ内のデータ操作(取得、共有、保存、検索)を簡素化するライブラリです。 ネットワーク経由でデータをいつ取得するか、メモリとディスクのどっちにキャッシュするか、データをKotlinのFlowで返却するかなどを簡単に実装することができます。
最近ではネットワークを最適化することが推奨されており、データをキャッシュしてオフラインでも使用できるようにしたり、不要なネットワークリクエストを防ぐ必要があります。 今回はネットワークを最適化するための第一歩として、Storeを用いて不要なネットワークリクエストを防ぐ対応を行ったのでその話をしていきます。
導入について
ライブラリをアプリへ導入する方法はStoreのREADME.mdに記載されているので、ここでは割愛させていただきます。
導入方法
不要な通信を防ぐために導入する目的であれば、実装は非常に簡単です。 弊社のコードを具体例に説明していきます。
弊社のアプリではユーザーが自分の所属している高校を設定することができます。 その高校一覧をサーバから取得してUI上に表示している箇所が下記のコードとなります。
Repository層
class HighSchoolsRepository(private val service: HighSchoolsService) { suspend fun index(): List<HighSchool> = service.index() }
ViewModel層
class StudyGoalHighSchoolViewModel(private val repository: HighSchoolsRepository) : ViewModel() { val highSchoolList = MutableLiveData<List<HighSchool>>() init { viewModelScope.launch { runCatching { repository.index() }.onSuccess { highSchoolList.value = it } } } }
Activity層
class StudyGoalHighSchoolActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel.highSchoolList.observe(this) { // リストとして表示 } } }
上記のコードでStoreを導入する場合、変更するのはRepository層のみです。 導入後のRepository層が下記となります。
class HighSchoolsRepository(private val service: HighSchoolsService) { private val store = StoreBuilder .from(Fetcher.of { service.index() }) .build() suspend fun index(): List<HighSchool> = store.get() }
APIを叩く際に引数を設定しませんでしたが、高校一覧取得APIに都道府県コードを設定する場合も簡単に実装することができます。
class HighSchoolsRepository(private val service: HighSchoolsService) { private val store = StoreBuilder .from(Fetcher.of { locationCode: Int -> service.index(locationCode) }) .build() suspend fun index(locationCode: Int): List<HighSchool> = store.get(locationCode) }
導入解説
Repository層からAPIを叩く際にStoreを導入しましたが、何故これで通信が削減されるか説明していきます。
まず、Storeとはアプリ内のデータ操作を簡素化するライブラリです。 APIを叩く箇所にStoreを導入するだけでは実装前後で差分は生まれません。 差分が出るのはStoreからどのようにデータを取り出すかによって決まります。
Storeからデータを取り出す方法は、現状では下記の3通りあります。
- Store.get(key: Key):メモリ内のキャッシュかsourceOfTruthからデータ取得、取得できない場合はネットワークからのデータ取得
- Store.fresh(key: Key):ネットワークからデータ取得
- Store.stream():Storeのデータが更新されるとFlow
でデータ返却
Storeを導入してもfresh(key: Key)でデータ取得を行えば、導入前のコードと同様の動作となります。 しかし、get(key: Key)でデータ取得を行うことでメモリ内のキャッシュかsourceOfTruthからデータを取得するため、ネットワークからデータ取得する回数を削減することができます。 メモリ内のキャッシュの生存期間はデフォルトで100個のアイテムを24時間に設定されています。*1
まとめると、弊社の導入後のコードではget(key: Key)を用いているため、24時間以内であれば再び通信することはなく通信量が削減されています。
メリット・デメリット
導入解説で細かく説明しましたが、メリットデメリットを簡単にまとめたいと思います。
メリット:
- メモリ内のキャッシュに保存できるため通信量が削減できる
- 端末がオフラインでもデータを表示することができる
- Flow
でデータをリアクティブに流したい場合やローカルDBに保存したい場合など拡張しやすい - 導入が非常に簡単
デメリット:
- サーバのデータが頻繁に変わるAPIは導入に向かない
- APIごとにStoreBuilderからStoreを作成するためコード量がだいぶ増える
まとめ
今回は通信量を削減することに絞ってStore導入を説明しましたが、拡張性が高く他にも多くのことができます。 弊社のアプリでは14箇所のAPIでStoreを導入し通信量の削減をしました。
今後はFlow
*1:生存期間はMemoryPolicyを用いて設定変更することもできます