Android Architecture Components でAndroid開発のアーキテクチャを知る


この記事は CBcloud Advent Calendar 2019 18日目の記事です。

対象読者

  • Android開発を少しでも実施したことがある人
  • Android開発に興味がある方

1. Android開発とアーキテクチャについての歴史

Android開発当初はEclipseと専用プラグイン(Android Development Tools)で開発されていました。
2013年5月15日に開催されたGoogle I/OでAndroid Studioが発表されました。

AndroidをEclipseで開発したことがありますが、重すぎてとても快適な開発とは言えませんでしたね...

その後、2017年のGoogle I/OでJavaに変わってKotlinで開発できるようになったり、2019年にはKotlinファーストと銘打ってどんどんモダンな開発者にやさしい開発環境になっていきました。

Kotlin on Android. Now official

Google I/O 2019: Empowering developers to build the best experiences on Android + Play

その中でもAndroid Architecture Componentsの発表はAndroid開発者にとって大きい発表だったと思います。
それまで公式では存在しなかったアーキテクチャについての公式ライブラリが初めて提供されました。
Architecture Components - Introduction (Google I/O '17)

2. Android Architecture Componentsとは?

アプリのアーキテクチャ ガイドに書いてある下記の一文がAndroid Architecture Componentsを表す上でもっとも適した文章だと思います。

最も重要な原則は関心の分離です。すべてのコードを 1 つの Activity または Fragment 内に記述するのはよくある間違いです。これらの UI ベースのクラスには、UI やオペレーティング システムとのやり取りを処理するロジックのみを含めます。これらのクラスをできる限りシンプルに保つことで、ライフサイクルに関連する多くの問題を回避することができます

アプリのアーキテクチャ ガイド

 データの取得

データの取得ではRetrofit(ほぼ公式)のAPIClientを実行し、データの取得を行います。

fun getTeamList(): LiveData<List<Team>> {
    val data = MutableLiveData<List<Team>>()

    footBallService.getTeamList().enqueue(object : Callback<TeamsResponse>{
        override fun onResponse(call: Call<TeamsResponse>, @Nullable response: Response<TeamsResponse>) {
            Log.d("TeamsResponse", response.toString())
            val response: TeamsResponse? = response.body()
            Log.d("TeamsResponse", response?.teams.toString())
            data.postValue(response?.teams)
        }

        override fun onFailure(call: Call<TeamsResponse>, t: Throwable) {
            Log.d("TeamsResponse", t.toString())
            data.postValue(null)
        }
    })

    return data
}

Modelに関しては一般的な形であると思いますが、下記の形でデータを扱います。

data class Team {
    val id:Int
    var area:Area
    var name:String
    var shortName:String
    var crestUrl:String
}

データの取り回し(ViewModel)

取得されたデータをLiveDataで保管し、Fragmentから監視できるようにします。

class TeamListViewModel(application: Application) : AndroidViewModel(application) {

    val teamListObservable: LiveData<List<Team>>

    init {
        teamListObservable = FootballRepository.instance.getTeamList()
    }
}

データのbinding(Fragment)

ここからはすべて図で言うと一番上位のActivity/Fragment での話になります。

FragmentでViewModelが保有している監視対象のLivedataをよびだします。
ここでObserverがイベントを検知したタイミングでチーム一覧をAdapterへバインディングします。

private fun observeViewModel(viewModel: TeamListViewModel) {


    //オブザーバーは、STARTED かRESUMED状態である場合にのみ、イベントを受信する
    viewModel.teamListObservable.observe(this, Observer { teams ->
        if (teams != null) {
            requireNotNull(binding).isLoading = false
            teamsAdapter?.setTeamList(teams)
        }
    })
}

Adapter内でのデータセット

Adapterでは受けわたされたデータをDataBindingによってViewにマッピングします。
自動生成されたDataBindingのclassを指定する必要があるので、次にあるxmlを先に用意してビルドしておく必要があります。

// データをsetする
fun setTeamList(teamList: List<Team>) {
       this.projectList = teamList
       result.dispatchUpdatesTo(this)
    }
}

// データをview内のitemに当てはめる
override fun onCreateViewHolder(parent: ViewGroup, viewtype: Int): TeamViewHolder {
    val binding =
            DataBindingUtil.inflate(
                    LayoutInflater.from(parent.context),

                    R.layout.project_list_item, parent,
                    false) as TeamListItemBinding   
                //自動生成されたDataBinding用のjavaclass


    return TeamViewHolder(binding)
}

xmlによるDataBindingのViewの表記

こちらでついにAPIが取得してきたデータがViewに当てはめられます。
xml内ではTeamモデルの形のみ意識すれば良いので

最も重要な原則は関心の分離です。

というアーキテクチャの導入目的を達成できていると言えると思います。

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable name="team" type="com.entaku.football.service.model.Team"/>
    </data>

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginStart="20dp"
        android:layout_marginEnd="20dp"
        app:cardUseCompatPadding="true">

        <LinearLayout
            android:layout_marginStart="20dp"
            android:layout_marginEnd="20dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="5dp"
            android:orientation="vertical">

            <TextView
                android:id="@+id/name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:contentDescription="test"
                android:textStyle="bold"
                android:textSize="20dp"
                android:text="@{team.name}"/>
        </LinearLayout>

    </androidx.cardview.widget.CardView>
</layout>

ここまでのソースをGitHubにまとめてありますので、宜しければ参考にしてください。
GitHub

3.まとめ

  • Android開発にてアーキテクチャ ガイド発表されたのは2017年とまだ2年程度
  • Android Architecture Components によって「関心の分離」が実現可能
  • Googleは神

以上ここまで読んでいただきありがとうございました!
私がまとめた中で間違いやこんな内容をもう少し書いてもらいたいなどありましたらお気軽にコメントお願いします!