안드로이드) ViewModel

밍나·2022년 5월 27일
0

Android

목록 보기
34/36

✏️ ViewModel

1. ViewModel

  • ViewModel 클래스는 생명 주기를 인식하며, UI와 관련된 데이터를 저장하고 관리한다.
  • ViewModel 클래스는 화면 회전 같은 구성 변경에서도 살아남아 데이터를 보존한다.
    • 사용자에의해 시스템이 UI 컨트롤러를 파괴하거나 재생성하면, 일시적으로 UI 관련 데이터를 모두 잃는다.
    • 간단한 데이터는 onSaveInstanceState()로 저장하고 bundle로부터 데이터를 되살릴 수 있지만 큰 데이터에는 적합하지 않다.
    • 또한 사용자의 행동에 반응하고, UI로 시스템과의 상호 작용을 해야하는 UI 컨트롤러가 데이터를 로드하는 것까지 맡으면 클래스가 비대해져 이를 분리하는 것이 효율적이다.

2. ViewModel 구현하기

  • ViewModel 객체는 구성 변경에도 자동으로 유지되므로, ViewModel이 보유한 데이터는 다음 액티비티나 프래그먼트 인스턴스에서 즉시 사용할 수 있다.
  • 아래 예제에서는 ViewModel이 UI 컨트롤러 대신 사용자 목록을 불러오고 데이터를 유지한다.
    • 불러온 데이터는 LiveData로 관리하기를 권장한다.
class MyViewModel : ViewModel() {
    private val users: MutableLiveData<List<User>> by lazy {
        MutableLiveData<List<User>>().also {
            loadUsers()
        }
    }

    fun getUsers(): LiveData<List<User>> {
        return users
    }

    private fun loadUsers() {
        // 비동기적으로 사용자 목록을 불러온다.
    }
}
  • 액티비티에서는 아래와 같이 데이터에 접근할 수 있다.
class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // onCreate에서 ViewModel을 생성한다.
        // 액티비티가 재생성되어 onCreate()가 또 호출되어도 이전에 만든 ViewModel 인스턴스를 가져온다.

        val model: MyViewModel by viewModels()
        model.getUsers().observe(this, Observer<List<User>>{ users ->
            // LiveData를 관찰해서 사용자 목록을 얻는다.
        })
    }
}
  • 액티비티가 재생성되더라도 액티비티가 최초에 생성한 ViewModel 인스턴스를 그대로 가져오고, ViewModel을 생성한 액티비티가 종료되어야 ViewModel의 onCleared()가 호출되어 리소스를 정리한다.
    • ViewModel은 View나 LifecycleOwner보다 오래 지속되도록 설계되었다.
    • 또한 View나 Lifecycle에 의존성이 없다.
  • ViewModel 내부에서 생명 주기 이벤트를 구현하도록 LifecycleObserver를 포함할 순 있지만, ViewModel 내부에서 LiveData의 변경 사항을 관찰해서는 안된다.
  • ViewModel에서 Application Context가 필요하면 AndroidViewModel 클래스를 사용할 수 있다.

3. ViewModel의 생명 주기

  • 생명 주기를 인식하는 ViewModel 객체를 얻으려면 Lifecycle을 ViewModelProvider에 넘겨야 한다.
  • ViewModel은 Lifecycle이 영구적으로 없어질 때까지(activity에서는 activity가 끝날 때까지, fragment에서는 fragment가 분리될 때까지) 메모리에 남아 있다.
  • 아래 그림은 액티비티가 회전하고 종료하기까지 생명 주기와 액티비티와 연관된 ViewModel의 생명주기다.
  • 일반적으로 ViewModel 객체의 생성은 액티비티의 첫 onCreate() 호출에서 이뤄지고, 구성 변경에 의해 여러번 액티비티가 재생성되어 onCreate()가 또 호출되어도 최초에 생성된 ViewModel 인스턴스를 유지한다.
  • ViewModel은 액티비티가 종료되고 파괴된 후에 onCleared()가 호출되어 리소스를 정리한다.

4. 프래그먼트 간 데이터 공유

  • Activity Scope로 생성한 ViewModel은 여러 프래그먼트 사이에서 데이터를 공유하고 상호 작용할 수 있도록 한다.
  • 아래 예제는 MasterFragment와 DetailFragment가 SharedViewModel을 이용해 데이터를 공유하는 것이다.
class SharedViewModel : ViewModel() {
    val selected = MutableLiveData<Item>()

    fun select(item: Item) {
        selected.value = item
    }
}
class MasterFragment : Fragment() {

    private lateinit var itemSelector: Selector
    private val model: SharedViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        itemSelector.setOnClickListener { item ->
            model.select(item)
        }
    }
}
class DetailFragment : Fragment() {

    private val model: SharedViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
            // UI 갱신
        })
    }
}
  • 프래그먼트에서 뷰 모델을 생성하는데 사용한 액티비티를 얻고, 이를 사용해 ViewModelProviders로부터 공통된 SharedViewModel 인스턴스를 얻을 수 있다.
  • 이 방식에는 아래와 같은 이점이 있다.
    • 전통적인 방법은 이벤트를 전달하는 interface를 구현해 액티비티가 중간 연결 역할을 했지만, ViewModel을 사용하면 액티비티는 아무것도 안해도 된다.
    • 프래그먼트는 SharedViewModel을 사용할 때 선행되는 작업이 없다. 프래그먼트 중 하나가 사라져도 잘 작동된다.
    • 각 프래그먼트는 자체 수명 주기가 있으며, 다른 프래그먼트에 영향을 주지 않는다. 한 프래그먼트가 다른 프래그먼트를 대체해도, UI는 아무 문제 없이 계속 작동한다.

5. ViewModel 사용 시 주의점

(1) ViewModel은 절대 액티비티 같은 Context를 참조하면 안된다

  • 액티비티는 언제든 파괴되고 재생성될 수 있지만, ViewModel은 유지된다.
  • 이때 이전에 파괴된 액티비티를 ViewModel에 유지하면 메모리 누수가 발생한다.
  • 그러므로 Context가 필요하다면 AndroidViewModel을 사용한다.

(2) ViewModel에서 안드로이드 프레임워크 코드를 참조하면 안된다

  • 프레임워크 코드를 참조하면 단위 테스트가 힘들어진다.
  • ViewModel은 View에 표현할 최소한의 데이터만 갖고, 일반적인 비즈니스 로직은 다른 계층에서 수행한다.

(3) Dagger2와 ViewModel의 사용을 신중히 한다

  • Dagger2는 자신만의 Scope를 지정하고 인스턴스를 관리하며, ViewModel 또한 스스로 인스턴스를 관리한다.
  • 둘의 Scope는 다르므로 Dagger2의 오브젝트 그래프에서 제공하는 인스턴스와 ViewModel이 같이 사용되는 경우 액티비티가 재생성됐을 때 객체의 동일성이 깨지기 쉽다.

(4) 리소스 아이디를 참조해 올바른 언어의 문자열을 참조한다

  • ViewModel에서 getString(int)을 통해 문자열을 관리하는 경우, 시스템 언어를 변경해도 이전 언어의 문자열이 그대로 남는다.
  • ViewModel과 View가 바인딩될 때, 리소스 아이디를 참조해 올바른 언어의 문자열을 참조한다.
profile
🤗🤗🤗

0개의 댓글