Studyplus Engineering Blog

スタディプラスの開発者が発信するブログ

DropboxのStoreで通信量を削減しました

こんにちは、モバイルクライアントグループの隅山です。 前回は両OS開発についてのブログを書きましたが、今回はDropboxのStoreを用いてAndroidアプリの通信量を削減した話をしていきます。

Storeについて

まず、DropboxのStoreとはアプリ内のデータ操作(取得、共有、保存、検索)を簡素化するライブラリです。 ネットワーク経由でデータをいつ取得するか、メモリとディスクのどっちにキャッシュするか、データをKotlinのFlowで返却するかなどを簡単に実装することができます。

最近ではネットワークを最適化することが推奨されており、データをキャッシュしてオフラインでも使用できるようにしたり、不要なネットワークリクエストを防ぐ必要があります。 今回はネットワークを最適化するための第一歩として、Storeを用いて不要なネットワークリクエストを防ぐ対応を行ったのでその話をしていきます。

導入について

ライブラリをアプリへ導入する方法はStoreのREADME.mdに記載されているので、ここでは割愛させていただきます。

github.com

導入方法

不要な通信を防ぐために導入する目的であれば、実装は非常に簡単です。 弊社のコードを具体例に説明していきます。

弊社のアプリではユーザーが自分の所属している高校を設定することができます。 その高校一覧をサーバから取得して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を用いてデータをリアルタイムに更新したり、ローカルDBとのデータ操作部分に導入していきたいと思います。 他の導入方法で発見があったらまたブログでまとめていきます。

*1:生存期間はMemoryPolicyを用いて設定変更することもできます