2024年5月31日金曜日

Android RecyclerViewを使ったコンテンツ一覧の実装方法を解説

どうも。どっことです。今回はRecyclerViewを使ったコンテンツ一覧の表示方法を解説します。

RecyclerViewを使ったコンテンツ一覧の表示

コンテンツの一覧表示する実装方法はいくつかありますが、よくある一般的なものはListViewRecyclerViewです。特にRecyclerViewはリスト表示だけでなく、グリッド表示や横スクロールコンテンツにもこれ一つで対応できるので、実装パターンさえ理解できていれば非常に融通が利くコンポーネントです。今回は、そんな融通が利くRecyclerViewの実装パターンについて解説します。

RecyclerViewを使ってコンテンツ一覧表示を実装する

シンプルな表示の実装は大きく以下の流れとなります。

  1. RecyclerViewを配置
  2. LayoutManagerを設定
  3. RecyclerView.Adapterを継承したCustomAdapterクラスを実装
  4. RecyclerView.ViewHolderを継承したCustromViewHolderクラスを実装
  5. CustomViewHolderCustomAdapterを繋ぎ込み
  6. RecyclerViewCustomAdapterクラスを繋ぎ込み

順に解説していきます。

RecyclerViewを配置

まずはRecyclerViewを画面レイアウトに配置します。ListViewを配置するのと同じ要領ですね。

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

LayoutManagerを設定

次にLayoutManagerを設定します。LayoutManagerRecyclerViewに対して、コンテンツをどのように配置するかを指定してくれるマネージャークラスです。LayoutManagerには、例えば以下があります。

  • 縦横にコンテンツを配置するLinearLayoutManager
  • グリッドにコンテンツを配置するGridLayoutManager
  • キーワードなどを柔軟に並べてくれるFlexBoxLayoutManager

今回はLinearLayoutManagerを使って、縦並びのコンテンツ配置を指定します。

指定方法は2つ。コードで設定する方法と、レイアウトで設定する方法です。

コードで設定する方法

コードからLayoutManagerを設定する方法です。LinearLayoutManagerのインスタンスを生成し、RecyclerView.setLayoutManager()で設定します。

val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.layoutManager = LinearLayoutManager(this)

レイアウトで設定する方法

レイアウトXMLから設定する方法です。app:layoutManagerで設定するだけです。

<androidx.recyclerview.widget.RecyclerView 
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

RecyclerView.Adapterを継承したCustomAdapterクラスを実装

次はRecyclerViewで表示する要素を管理しているAdapterクラスを実装していきます。RecyclerView.Adapterを継承した、CustomAdapterクラスを作成します。

class CustomAdapter : RecyclerView.Adapter<ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        TODO("このあと実装")
    }

    override fun getItemCount(): Int {
        TODO("このあと実装")
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        TODO("このあと実装")
    }
}

次の項目で、実際に表示するコンテンツ要素を実装するのですが、それができたらこちらのCustomAdapterクラスと繋ぎ込みをしていきます。

RecyclerView.ViewHolderを継承したCustomViewHolderクラスを実装

では、RecyclerViewで表示するコンテンツ要素部分を実装します。RecyclerView.ViewHolderを継承した、CustomViewHolderクラスを作成します。

レイアウトファイルの作成

レイアウトファイルを作成します。これはあとでCustomViewHolderに渡します。ここではファイル名をview_holder_sample.xmlとしておきます。

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/text_view"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:gravity="center"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

CustomViewHolderの実装

RecyclerView.ViewHolderを継承したCustomViewHolderクラスを実装します。細かい実装は、後述の繋ぎ込みをしながら実装します。

class CustomViewHolder(itemView: View): ViewHolder(itemView) {
    // まずはこれだけ
}

CustomAdapterCustomViewHolderを繋ぎ込み

ここまでできればもうすぐです。実装したCustomAdapterCustomViewHolderを繋ぎ込んでいきましょう。実装が必要な箇所は大きく2箇所、onCreateViewHolderonBindViewHolderです。

繋ぎ込み1: onCreateViewでCustomViewHolderを生成

onCreateViewHolderは表示するCustomViewHolderを生成する箇所です。ここではあくまで生成だけで、実際に表示する要素を当てこむ箇所がonBindViewHolderとなります。今回はサンプルのため、表示件数は10件とします。

class CustomAdapter : RecyclerView.Adapter<ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        // onCreateViewHolderで、CustomViewHolderを生成する。
        return CustomViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.view_holder_sample, parent, false))
    }

    override fun getItemCount(): Int {
        return 10 // 今回はサンプルのため、10件だけ表示することとします。
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        TODO("このあと実装")
    }
}

繋ぎ込み2: CustomViewHolderでの表示処理を追加実装

先ほど追加したCustomViewHolderに表示用のTextViewへの参照を持たせておきましょう。

class CustomViewHolder(itemView: View): ViewHolder(itemView) {
    val textView: TextView = itemView.findViewById(R.id.text_view)
}

繋ぎ込み3: onBindViewHolderで表示内容を当て込み

先ほど追加したCustomViewHolderTextViewを参照し、onBindViewHolderで表示内容を当てこんでいきます。

class CustomAdapter : RecyclerView.Adapter<ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        // onCreateViewHolderで、CustomViewHolderを生成する。
        return CustomViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.view_holder_sample, parent, false))
    }

    override fun getItemCount(): Int {
        return 10
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        if (holder is CustomViewHolder) {
            holder.textView.text = position.toString()
        }
    }
}

RecyclerViewCustomAdapterを繋ぎ込み

あとは、画面レイアウトに配置したRecyclerViewCustomViewHolderを繋ぎこむだけです。

val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.adapter = CustomAdapter()

実際に実装した結果は以下のように表示されるかと思います。

今回はひとまずこれで完成です。お疲れ様でした。

さて、上記でシンプル(=必要最低限)な実装を解説しましたが、実際にRecyclerViewを利用する場合、以下のような機能も実現したい場合がほとんどだと思います。

  • 表示要素は動的にしたい。
    • 例えばTODOリストなどに使うなら、解説した実装だと満たせません。
  • クリックしたらその要素に対応する画面を表示したい。
    • ListViewではOnItemClickListenerというインターフェースが用意されていますが、RecyclerViewは自分で定義・実装が必要になります。

まとめ

今回はRecyclerViewに関するシンプルな実装を解説しました。さまざまな使い方ができるRecyclerViewの使い方をマスターしたい人は最初の一歩として参考にしていただけると幸いです。ただ、上記の通りまだまだ実際の利用にはアプローチが足りていないので、次回は上記のようなより実用に近い機能の実装方法について解説したいと思います。

2024年5月7日火曜日

Android Retrofitを使った情報取得する実装を解説

どうも。どっことです。今回は、Retrofitを使った情報取得する実装について解説します。

Retrofitを使った通信処理の実装

他の言語ではいくつか紹介していましたが、そもそもAndroidを紹介していないことに気づきました。そんなわけで今回は、Androidのデファクトスタンダードの一つとなっているRetrofitというライブラリを使った通信処理の実装を解説します。

実装手順

以下の手順で実装していきます。

  • build.gradleにライブラリを追加
  • AndroidManifest.xmに通信するパーミッションを追加
  • Retrofitによる通信処理の実装1
  • 通信するためのIFと、レスポンスデータクラスを追加
  • Retrofitによる通信処理の実装2

順に解説していきます。

build.gradleにライブラリを追加

まずはbuild.gradledependenciesに以下を追加します。

dependencies {
    // moshiライブラリ。Jsonをデータオブジェクトに変換してくれる。
    implementation("com.squareup.moshi:moshi-kotlin:1.15.1")
    // retrofitライブラリ。通信処理してくれるライブラリ。
    implementation("com.squareup.retrofit2:retrofit:2.11.0")
    // moshi-converter。 retrofit で moshi をいい感じに使ってくれるためのもの。
    implementation("com.squareup.retrofit2:converter-moshi:2.11.0")
}

AndroidManifest.xmに通信するパーミッションを追加

アプリ上で外部通信する場合、AndroidManifest.xmlにパーミッションの宣言が必要です。

以下を追加します。

<uses-permission android:name="android.permission.INTERNET" />

Retrofitによる通信処理の実装1

今回利用するAPIとして、サンプルAPIを公開している以下を利用させていただきました。

まずは実際に通信処理をするインスタンスを作るため、Retrofitのインスタンスを生成しましょう。

val retrofit = Retrofit.Builder()
  .baseUrl("https://api.sampleapis.com")
  .addConverterFactory(
      MoshiConverterFactory.create(
          Moshi.Builder()
              .add(KotlinJsonAdapterFactory())
              .build()
      )
  .build()
  • baseUrlは接続先URLのドメインを指定します。パスは後述するインターフェース側に記載します。
  • addConverterFactoryは、通信結果により取得したJsonを解析させるためのライブラリを指定します。今回はMoshiライブラリを指定しています。

用意したretrofitオブジェクトは後ほど利用します。

通信するためのIFと、レスポンスデータクラスを追加

通信するためのインターフェースと、レスポンスデータクラスを追加します。

インターフェースはメソッド(@GET)やretrofitオブジェクトのドメインからのパス(/coffee/hot)を指定し、レスポンスとしてどのようなデータを返却してもらうかを定義します。

interface ApiInterface {
  @GET("/coffee/hot")
  fun hotCoffee(): Call<List<Coffee>>
}

またレスポンスデータとしてCoffeeクラスのリストを定義します。今回は簡単のため、titleだけを解析対象としてデータ定義します。

data class Coffee(val title: String)

他のデータも取得したい場合は、対応するものをメンバ変数に定義すれば解析してくれます。

Retrofitによる通信処理の実装2

先ほど追加したインターフェースを渡して、実際に通信処理してくれるインスタンスを生成しましょう。

val api = retrofit.create(ApiInterface::class.java)

あとは、生成したインスタンスで通信処理を依頼します。enqueue()メソッドで通信処理を非同期に依頼します。

api.hotCoffee().enqueue(object : Callback<List<Coffee>> {
    override fun onResponse(p0: Call<List<Coffee>>, response: Response<List<Coffee>>) {
        Log.d("RetrofitActivity", "response.isSuccessful:${response.isSuccessful}")
        Log.d("RetrofitActivity", "response.code:${response.code()}")
        Log.d("RetrofitActivity", "response.message:${response.message()}")
        Log.d("RetrofitActivity", "response.raw:${response.raw()}")
        Log.d("RetrofitActivity", "response.body:${response.body()}")
    }
    override fun onFailure(p0: Call<List<Coffee>>, error: Throwable) {
      // エラーハンドリング
      Log.w("RetrofitActivity", "onFailure", error)
    }
})

なお同じ通信処理の依頼でexecute()というメソッドがありますが、こちらは同期的に通信処理を依頼します。Androidではメインスレッドで通信処理を実施するとクラッシュしてしまうので、利用するタイミングの検討が必要となります。

まとめ

今回は、Retrofitを使った情報取得する実装について解説しました。通信処理による情報取得・送信はモバイルアプリでは必須な機能となるので、ぜひ参考にしてくださいね。

参考

2024年5月5日日曜日

Android mockito-kotlinを使ったテストコードの書き方を紹介

どうも。どっことです。今回は、mockito-kotlinライブラリを使ったKotlinでの単体テストの書き方を紹介します。

mockito-kotlinを使ったモック化

CI/CDの重要性が注目されて久しいですが、単体テストをはじめとしたテストコードを実装できるエンジニアはまだまだ多くありません。またテストコードは実装経験あるけど、いざ実装しようとした時に「あれってどうやるんだっけ」となるような、テストコードの実装がまだ手に馴染んでいない人も多いかと思います。

今回は単体テストのテストコードを実装する上で必須のテクニックとなるモック化について紹介したいと思います。

モック化とは

他の機能の影響によりテストが不安定な状態となることを防ぐため、あらかじめ固定の値を返したり、特定の振る舞いをさせるように指定しておくことです。

実装方法

以下の順に解説していきます。

  • 使用するライブラリ(mockito-kotlinライブラリの紹介)
  • 導入方法
    • 実装方法

    使用するライブラリ

    オブジェクトをモック化するにあたり、JavaやKotlinではさまざまなモック化ライブラリが公開されています。今回はその中のひとつであるmockito-kotlinを使った導入・実装方法を解説していきます。

    mockito-kotlin

    もともとJavaのモックフレームワークとして利用されているMockitoライブラリを、Kotlinでも簡単に利用できるような機能を具備しているライブラリがmockito-kotlinです。公式ページを参考欄に載せておくので、詳しい情報をお探しの方はそちらを参照してください。

    導入方法

    いつものごとく、build.gradleのdependenciesに追記します。以下を追加してください。

    dependencies {
      testImplementation "org.mockito.kotlin:mockito-kotlin:5.3.1"
    }

    実装方法

    それでは、モック化する実装方法について解説します。

    モック化の実装①戻り値の固定化

    戻り値を固定化する実装は以下です。 mockでモックするクラス、onでモックするメソッドを指定します。そしてdoReturnで戻り値を指定します。

    val mock = mock<Sample> {
        on { sample() } doReturn "sample"
    }

    モック化の実装②例外

    メソッドが呼ばれた時に特定の例外を投げるようなモックの実装は以下です。doThrowで例外を指定します。

    val mock = mock<Sample> {
        on { sample() } doThrow IllegalStateException()
    }

    モック化の実装③振る舞いの指定

    メソッドが呼ばれた時に特定の振る舞いをさせるモックの実装は以下です。doAnswerで具体的な振る舞いを指定します。

    val mock = mock<Sample> {
        on { sample() } doAnswer {
            // do anything.
        }
    }

    まとめ

    今回は、テストコードを実装する上で必要不可欠なテクニックであるモック化をmockito-kotlinライブラリを使って実装する方法について解説しました。他の機能に依存しない疎結合なテストコードを実装するためにモック化は重要なテクニックとなるので、手に馴染むまで何度も実装しましょう。

    参考

    移行予定

    どうも。どっことです。 タイトルの通りですが、諸事情により GitHubPage に移行予定です。 https://mkt120.github.io/ この備忘録に記載の内容を転記しつつ、今後はこちらのページを更新していく予定です。