12
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

結局のところRecyclerViewのコールバックはどう書くのがベストなのよ?

Last updated at Posted at 2018-08-16

 
 RecyclerViewの要素をクリックした時の処理をActivity・Fragmentでコールバックとして受けとる。
 Android開発をやってると、かなりの頻度で書くことになるこの処理、みなさんはどのように書いていますか?

Interfaceによるコールバック

これは俺の推測でしかないのですが、おそらく一番一般的というか多数派なのが

クリック処理のコールバックを書いたinterfaceを実装

interfaceをActivityに継承し、RecyclerViewのAdapterに渡す

この書き方だと思います。

TestListener.kt
interface TestListener {

    /**
     * RecyclerViewのセルのボタンをクリックしたら
     */
    fun onClickTestButton(text: String)

}
TestAdapter.kt
class TestAdapter(var list: List<String>, private val listener: TestListener): RecyclerView.Adapter<TestAdapter.ViewHolder>() {

    /**
     * セルのインスタンス作成
     * @param parent
     * @param viewType
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ItemTestBinding.inflate(LayoutInflater.from(parent.context), parent, false).let {
            TestAdapter.ViewHolder(it)
        }
    }

    /**
     * セルごとの処理
     * @param holder
     * @param position
     */
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.binding.apply {
            testButton.text = list[position]
            testButton.setOnClickListener { listener.onClickTestButton(list[position]) }
        }
    }

    /**
     * 表示するアイテムの数
     */
    override fun getItemCount() = list.size

    /**
     * ViewHolder
     * @param binding
     */
    class ViewHolder(val binding: ItemTestBinding): RecyclerView.ViewHolder(binding.root)

}

TestActivity.kt

    class TestActivity: RxAppCompatActivity() , TestListener{

    private lateinit var binding: ActivityTestBinding
    private lateinit var adapter: TestAdapter
    private var list = listOf("aaa", "bbb" ,"ccc") //RecyclerViewに表示させる適当な値

    /**
     * 初期化
     * @param savedInstanceState
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_test)
        initRecyclerView()
    }

    /**
     * RecyclerViewの初期化
     */
    private fun initRecyclerView() {
        adapter = TestAdapter(list, this)
        binding.testRecyclerView.apply {
            layoutManager = LinearLayoutManager(context)
            this.adapter = this@TestActivity.adapter
        }
    }

    /**
     * RecyclerView内のボタンがクリックされたら
     * @param text
     */
    override fun onClickTestButton(text: String) {
        Log.v("クリック", "$text")
    }

    companion object {
        /**
         * Activity起動
         * @param activity
         */
        fun start(activity: Activity){
            activity.startActivity(Intent(activity, TestActivity::class.java))
        }
    }
}


※今回はクラス名は基本的にTest〜という名前でつけてます

まあ、特に説明するような箇所もないと思いますが、testButtonってやつがクリックされた時の処理をInterfaceで定義してあげなきゃいけないのがちょっと面倒かなと思います。しかもRecyclerViewのクリック処理ってかなり頻出するので、何度も書いているうちに嫌気がさして来ますよね。

高階関数によるコールバック

 そこでおすすめしたいのが今回紹介する二つ目の手法。高階関数を渡して処理を移譲するやり方。
 このやり方を使うとInterfaceの実装が不要になるのでそれだけコードの量が減ります。今回はAdapterクラスのコンストラクタで高階関数を渡してみたいと思います。

TestAdapter.kt

class TestAdapter(var list: List<String>, private val onClickCallBack: (String) -> Unit): RecyclerView.Adapter<TestAdapter.ViewHolder>() {

    /**
     * セルのインスタンス作成
     * @param parent
     * @param viewType
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ItemTestBinding.inflate(LayoutInflater.from(parent.context), parent, false).let {
            TestAdapter.ViewHolder(it)
        }
    }

    /**
     * セルごとの処理
     * @param holder
     * @param position
     */
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.binding.apply {
            testButton.text = list[position]
            testButton.setOnClickListener { onClickCallBack(list[position]) }
        }
    }

    /**
     * 表示するアイテムの数
     */
    override fun getItemCount() = list.size

    /**
     * ViewHolder
     * @param binding
     */
    class ViewHolder(val binding: ItemTestBinding): RecyclerView.ViewHolder(binding.root)

}

TestActivity.kt

class TestActivity: RxAppCompatActivity(){

    private lateinit var binding: ActivityTestBinding
    private lateinit var adapter: TestAdapter
    private var list = listOf("aaa", "bbb" ,"ccc") //RecyclerViewに表示させる適当な値

    /**
     * 初期化
     * @param savedInstanceState
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_test)
        initRecyclerView()
    }

    /**
     * RecyclerViewの初期化
     */
    private fun initRecyclerView() {
        adapter = TestAdapter(list, onClickTestButton) //ここで関数オブジェクトを渡す
        binding.testRecyclerView.apply {
            layoutManager = LinearLayoutManager(context)
            this.adapter = this@TestActivity.adapter
        }
    }

    /**
     * RecyclerView内のボタンがクリックされたら呼ばれる
     */
    private val onClickTestButton: (String) -> Unit = {
        Log.v("クリック", "$it")
    }

    companion object {
        /**
         * Activity起動
         * @param activity
         */
        fun start(activity: Activity){
            activity.startActivity(Intent(activity, TestActivity::class.java))
        }
    }
}

 さっきよりもコード量が減りました。
 このように、高階関数(関数オブジェクト)を渡す方法だとInterfaceの実装が不要になります。せっかくKotlinは高階関数が使えるので利用しない手はないですね。

番外編:RxJavaを使ったコールバック

 最後に、RxJavaを使ったクリック処理のコールバックの実装方法を紹介したいと思います。
 Adapter内にPublishSubjectを定義してあげて、クリックされる旅にonNextで流してあげるようにします。あとは、ActivityからそのSubjectを購読すればコールバックを受け取れるようになります。実際に実装をみながら確認してみます。

TestAdapter.kt

class TestAdapter(var list: List<String>): RecyclerView.Adapter<TestAdapter.ViewHolder>() {

    /**
     * クリックした際のSubject
     */
    val onClickSubject: PublishSubject<String> = PublishSubject.create()

    /**
     * セルのインスタンス作成
     * @param parent
     * @param viewType
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ItemTestBinding.inflate(LayoutInflater.from(parent.context), parent, false).let {
            TestAdapter.ViewHolder(it)
        }
    }

    /**
     * セルごとの処理
     * @param holder
     * @param position
     */
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.binding.apply {
            testButton.text = list[position]
            testButton.setOnClickListener { onClickSubject.onNext(list[position]) }
        }
    }

    /**
     * 表示するアイテムの数
     */
    override fun getItemCount() = list.size

    /**
     * ViewHolder
     * @param binding
     */
    class ViewHolder(val binding: ItemTestBinding): RecyclerView.ViewHolder(binding.root)

}

TestActivity.kt

class TestActivity: RxAppCompatActivity(){

    private lateinit var binding: ActivityTestBinding
    private lateinit var adapter: TestAdapter
    private var list = listOf("aaa", "bbb" ,"ccc") //RecyclerViewに表示させる適当な値

    /**
     * 初期化
     * @param savedInstanceState
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_test)
        initRecyclerView()
        observableOnClickRecyclerView()
    }

    /**
     * RecyclerViewの初期化
     */
    private fun initRecyclerView() {
        adapter = TestAdapter(list)
        binding.testRecyclerView.apply {
            layoutManager = LinearLayoutManager(context)
            this.adapter = this@TestActivity.adapter
        }
    }

    /**
     * RecyclerViewのクリックを監視
     */
    private fun observableOnClickRecyclerView() {
        adapter.onClickSubject.subscribe {
            Log.v("クリック", it)
        }
    }

    companion object {
        /**
         * Activity起動
         * @param activity
         */
        fun start(activity: Activity){
            activity.startActivity(Intent(activity, TestActivity::class.java))
        }
    }
}

 このように、Rxを使うとAdapterのコンストラクタにコールバックを渡す必要すらなくなります。個人的にはこの書き方が一番好きですが、良い書き方なのかどうかはなんとも言えないです。
 今回紹介した3つの書き方のうち、それぞれのメリットやデメリットなどありましたらぜひ教えてください。
 また、間違った説明をしている箇所などがありましたらどんどん教えていただけると嬉しいです。

 最後まで読んでいただきありがとうございました。

12
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?