깨알 개념/Android

[Android] RecyclerView - notifyDataSetChanged()

interfacer_han 2024. 2. 28. 12:34

#1 이전 글

 

[Android] RecyclerView - Adapter에 인자(Argument) 전달

#1 이전 글 [Android] RecyclerView - 기초#1 ListView vs RecyclerView 화면에 요소(Item)을 100개 표현한다고 해보자. ListView나 GridView 등의 전통적인 Container Widget들은 이 100개의 아이템을 모두 불러온(load)다. 그

kenel.tistory.com

이전 게시글에선 Item의 List를 Adapter의 인자로 전달했었다. 이 때, 위 게시글의 완성된 앱을 수정해서 Item의 List가 여러 번 바뀌는 경우를 생각해보겠다.

 

#2 Item의 List의 빈번한 변경

#2-1 개요

위와 같이 맨 위의 버튼을 누르면, RecyclerView의 Item이 하나씩 랜덤으로 추가되게 앱을 수정한다.

 

#2-2 Activity 수정 (MainActivity.kt)

// package com.example.notifydatasetchanged

import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    companion object {
        val availableBurgerList = listOf<Menu>(
            Menu("Classic Cheeseburger", 6800),
            Menu("Bacon Deluxe Burger", 7800),
            Menu("BBQ Ranch Burger", 5800),
            Menu("Mushroom Swiss Burger", 6700),
            Menu("Double Stack Burger", 9800),
            Menu("Spicy Chicken Burger", 7900),
            Menu("Crispy Chicken Burger", 6800),
            Menu("Grilled Chicken Club", 9700),
            Menu("Veggie Burger Deluxe", 6800),
            Menu("Hawaiian Teriyaki Burger", 8900),
            Menu("Philly Cheese Steak Burger", 6200),
            Menu("Western BBQ Burger", 7300),
            Menu("Jalapeno Pepper Jack Burger", 7900),
            Menu("Turkey Avocado Burger", 6700),
            Menu("Black Bean Burger", 7600),
            Menu("Bacon Jalapeno Burger", 6700),
            Menu("Ultimate BBQ Bacon Burger", 6700),
            Menu("Chicken Bacon Ranch Burger", 6700),
            Menu("Chipotle Guacamole Burger", 6700),
            Menu("Breakfast Burger", 4700),
        )
    }

    val burgers = ArrayList<Menu>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)
        recyclerView.adapter = MyRecyclerViewAdapter(burgers) { menu: Menu -> itemClicked(menu) }

        val addBurgerButton = findViewById<Button>(R.id.addBurgerButton)
        addBurgerButton.setOnClickListener {
            burgers.add(availableBurgerList.random())
            recyclerView.adapter = MyRecyclerViewAdapter(burgers) { menu: Menu -> itemClicked(menu) }
        }
    }

    private fun itemClicked(menu: Menu) {
        Toast.makeText(
            this, "Selected Menu is ${menu.name}", Toast.LENGTH_LONG
        ).show()
    }
}

burgers가 변할 수 있어야한다. 따라서 원소 삭제 및 추가가 불가능한 List에서 ArrayList로 burgers의 데이터 타입을 바꿨다.

 

#2-3 문제점

위 코드는 Item이 변동될 때마다, 즉 버튼을 누를 때마다 Adapter가 새로 생성한다. 이는 메모리의 낭비다. Adapter는 이렇게 많이 생성될 이유가 없는 객체다.

 

#3 notifyDataSetChanged()

#3-1개요

 

RecyclerView.Adapter  |  Android Developers

androidx.appsearch.builtintypes.properties

developer.android.com

이 문제점을 해결할 수 있는 메소드가 RecyclerView.Adapter.notifyDataSetChanged()다. 이 메소드는 Adapter 내부의 데이터가 변경되었을 때, 해당 변경에 대한 처리를 요청한다. 아래는 notifyDataSetChanged() 메소드를 이용해 문제를 해결하는 과정이다.

 

#3-2 Adapter 수정 (MyRecyclerViewAdapter.kt)

...

class MyRecyclerViewAdapter(val clickListener: (Menu) -> Unit) :
    RecyclerView.Adapter<MyViewHolder>() {

    var menus = ArrayList<Menu>()

    ...
}

Adapter의 매개변수이자 프로퍼티였던 burgers를, 매개변수에서 제외하고 Adapter의 프로퍼티로만 둔다.

 

#3-3 Activity 재수정 (MainActivity.kt)

...

class MainActivity : AppCompatActivity() {

    companion object {
        val availableBurgerList = listOf<Menu>(
            ...
        )
    }

    val adapter = MyRecyclerViewAdapter { menu: Menu -> itemClicked(menu) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)
        recyclerView.adapter = adapter

        val addBurgerButton = findViewById<Button>(R.id.addBurgerButton)
        addBurgerButton.setOnClickListener {
            adapter.menus.add(availableBurgerList.random())
            adapter.notifyDataSetChanged()
        }
    }

    private fun itemClicked(menu: Menu) {
        Toast.makeText(
            this, "Selected Menu is ${menu.name}", Toast.LENGTH_LONG
        ).show()
    }
}

먼저, burgers 프로퍼티는 이제 Adapter에 있으므로, Activity의 burgers 프로퍼티는 제거한다. 그리고 제거한 Activity의 burgers 프로퍼티에 add()를 수행했던 버튼 클릭리스너를, Adapter의 burgers 프로퍼티에 add()를 수행하도록 수정한다. 마지막으로, 버튼 클릭리스너에 RecyclerView.Adapter.notifyDataSetChanged()를 넣어준다. 이러면 #2-2와 같은 동작을 수행하면서도, Adapter 인스턴스가 양산되지 않기 때문에 메모리는 더 적게 먹는다.

 

#3-4 비슷한 메소드들

 

RecyclerView.Adapter  |  Android Developers

androidx.appsearch.builtintypes.properties

developer.android.com

notifyDataSetChanged()보다 더 세부적인 변경에 대한 처리 메소드들도 존재한다. 더 많은 메모리를 아끼기 위해서라면 위 링크에서 해당 메소드들을 찾아 상황에 맞게 사용하면 된다. 가령, notifyItemInserted(Int) (아이템 추가), notifyItemRemoved(Int) (아이템 삭제), notifyItemChanged(Int) (아이템 업데이트)를 쓰면 된다. Int형 인자는 해당 작업들이 수행된 Item의 Index다.

 

#4 요약

RecyclerView.Adapter는 그 인스턴스가 많을 이유가 없는 클래스다.

 

#5 완성된 앱

https://github.com/Kanmanemone/android-practice/tree/master/recycler-view/NotifyDataSetChanged