Studyplus Engineering Blog

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

Android Architecture Componentsに感動した話

初めまして、Studyplus開発部のAndroidエンジニアの隅山です。今年5月からスタディプラスでAndroidアプリ開発を行っています。

本格的にAndroid開発を始めてまだ日が浅いので、初中級エンジニア目線で学んだことをまとめていきたいと思います。

背景

初めてAndroid開発を行った時感じたことが「Androidの画面表示関連の仕様の複雑さ」でした。

以前開発してた時もAndroidのライフサイクルや画面回転あたりで非常に苦しめられていました。しかし今回、Android Architecture Components(AAC)を学ぶ+導入することで、ライフサイクルや画面回転問題が簡単に解消されたのでAAC実装例を紹介します。

やったこと

今回、ユーザが入力する文字列に対応した検索結果を、リアルタイムでリスト表示する機能を実装しました。

実装としては画面にEditTextとListViewを用意し、EditTextの文字の変更通知に合わせてListViewのアイテムを更新するという仕組みとなります。AACを使わない場合と使う場合で大きく変わった点が「EditTextの文字変更通知」部分なのでそこに着目していきます。

AAC使わない場合

まずはAACを使わないやり方、TextWatcherで文字変更通知を受ける場合です。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val itemList = listOf("くろ", "あか", "あお", "しろ")
        val editText = findViewById<EditText>(R.id.edit_text)
        val listView = findViewById<ListView>(R.id.list_view)
        val arrayAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, itemList)
        listView.adapter = arrayAdapter

        editText.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(s: Editable?) {
                // 文字変更通知を受けて、ListViewの中身更新
                val list = if (s.isNullOrEmpty()) itemList else itemList.filter { it.contains(s) }
                arrayAdapter.clear()
                arrayAdapter.addAll(list)
            }
            override fun beforeTextChanged(s: CharSequence?, s: Int, c: Int, a: Int) {}
            override fun onTextChanged(s: CharSequence?, s: Int, b: Int, c: Int) {}
        })
    }
}

自分が感じたこの実装のメリット、デメリットは下記となります。

  • メリット
    • Androidの標準APIのみで書ける
  • デメリット
    • 画面回転考慮する必要がある
    • アクティビティの役割が大きすぎる
    • モダンな実装ではない

特に、画面回転問題を考慮する場合、onSaveInstanceStateなどでデータを保持する必要があります。 また、サーバーから検索結果を取得し表示するなどの機能拡張を行うと、アクティビティクラスのコード量も役割もどんどん増えてしまいます。

AAC使う場合

つぎにAACのViewModel+LiveDataで文字変更通知を受ける場合です。

class SampleViewModel : ViewModel() {
    private val itemList = listOf("くろ", "あか", "あお", "しろ")

    // serchTextは双方向BindingによりEditTextと接続
    val searchText = MutableLiveData<String>()
    val items: LiveData<List<String>> = Transformations.map(searchText) { text ->
        // ここで文字変更通知を受け、リストに表示するアイテム作成
        if (text.isNullOrEmpty()) itemList else itemList.filter { it.contains(text) }
    }
}
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val viewModel = ViewModelProviders.of(this).get(SampleViewModel::class.java)
        val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1)

        binding.lifecycleOwner = this
        binding.viewModel = viewModel
        binding.listView.adapter = adapter

        viewModel.items.observe(this, Observer { items ->
            // リスト表示アイテムを受け、ListViewに表示
            adapter.clear()
            items?.let { adapter.addAll(it) }
        })
    }
}

自分が感じたこの実装のメリット、デメリットは下記となります。

  • メリット
    • ViewModelがViewModelProviderで管理されているため画面回転されても情報が保持される
    • SampleViewModelで文字変更通知を受け、リストに表示するアイテムを作成するため、アクティビティは流れてきたアイテムを表示する役割のみ
    • モダンな実装のため、Google公式の最新アップデートに追随しやすい
  • デメリット
    • AACに関して勉強する必要がある

文字変更通知もLiveDataを使うと簡単に受けることができ、リストに表示するアイテムを自由にViewModel側でカスタマイズすることができます。今回は簡単な文字列リストを表示するだけですが、実際は検索文字を用いてサーバアクセスしたため、SampleViewModelでKotlin Coroutinesなどを使って実装しました。

まとめ

Androidでつまづきやすい画面回転によって情報が落ちてしまう問題や、クラスの役割が膨大になる問題をAACを導入することで解決できたのは感動しました。

今回は簡単な例を示すためリストに文字列表示するだけでしたが、実際の利用シーンでは検索文字列をサーバに投げてレスポンス結果をリストでリアルタイム表示しています。この際、Kotlin Coroutinesによる非同期処理や入力待ちの遅延処理を行なっています。AACを使わない場合だとアクティビティが肥大化して運用できない形になっていたため、今後の運用面も考慮して開発することができたのはよかったと思います。

AndroidのことやAACのことは同じチームメンバーの若宮さんや中島さんに教わりつつ開発することができたため、大きくつまづくことはほとんどなく非常に助かりました。Androidの画面回転やライフサイクルなどで詰まっている方は是非Android Architecture Componentsを導入してみてください。