Android/MVVM (based on AAC)

ViewModel

네모메모 2022. 7. 5. 20:41
반응형

ViewModel

안드로이드는 화면 회전과 같이 구성을 변경할 때

UI 컨트롤러를 제거하거나 다시 만들어지면서 UI 컨트롤러에 저장된 모든 일시적인 UI 관련 데이터가 삭제됩니다. 

 

cf) 그래서 기존에는 onSaveInstanceState() 메서드를 사용하여 데이터 삭제 전 보관하였으나 제한적이었습니다.

더보기

onSaveInstanceState() 메서드를 사용하여 onCreate()의 번들에서 데이터를 복원할 수 있습니다.

 

하지만 이 접근 방법은

1) 사용자 목록이나 비트맵과 같은 대용량일 가능성이 높은 데이터가 아니라, 직렬화했다가 다시 역직렬화할 수 있는 소량의 데이터에만 적합합니다.

2) 다른 문제는 UI 컨트롤러가 반환하는 데 시간이 걸릴 수 있는 비동기 호출을 자주 해야 한다는 점입니다. UI 컨트롤러는 비동기 호출을 관리해야 하며, 메모리 누수 가능성을 방지하기 위해 호출이 제거된 후 시스템에서 호출을 정리하는지 확인해야 합니다. 관리에는 많은 유지관리가 필요하며, 구성 변경 시 개체가 다시 생성되는 경우 개체가 이미 수행된 호출을 다시 호출해야 할 수 있으므로 리소스가 낭비됩니다.

 

 

 

(그래서 구글은 UI 컨트롤러 로직에서 뷰 데이터 소유권을 분리하는 방법이 훨씬 쉽고 효율적이라 생각하여)

따라서, 수명 주기를 고려하여 UI 관련 데이터를 저장하고 관리하도록 ViewModel 클래스가 탄생하였습니다.

 

 

이로 인해 이제

  • 생명 주기에 영향을 받지 않고 데이터를 유지할 수 있다.
  • UI 컨트롤러와 데이터가 분리된다. (Clean Arichtecture)
  • 프래그먼트 간의 데이터 공유가 쉬워진다.

 


 

ViewModel의 생명주기

※ 주의: ViewModel은 View, Lifecycle 또는 context 참조를 포함하는 클래스를 참조해서는 안 됩니다!!

ㄴ> 이유

더보기

뷰 모델은 액티비티나 프래그먼트보다 긴 생명주기를 가지고 있다.

액티비티는 종료와 생성을 반복하겠지만 뷰 모델은 쭉 살아있기 때문에 이미 종료되어 사라진 액티비티의 참조를 그만큼 가지고 있을 것이다.

ex) 화면 100번 회전 시 ViewModel이 이미 사라진 액티비티 참조 다 가지고 있다;;

 

쓸데없는 것이 메모리를 차지하고 있는 현상, 즉 Memory Leak이 발생하기 때문에 참조를 하면 안 된다는 것이다.

ㄴ>  LiveData 같은 LifecycleObservers 포함은 가능!

        그러나 LiveData처럼 수명 주기를 인식하는 Observable의 변경사항을 관찰하는 것은 절대안됨!!

 

ㄴ> AndroidViewModel을 상속받아  applicationContext는 참조를 해도 괜찮다.

       (∵ 액티비티의 생명주기가 아닌 애플리케이션의 생명주기를 가지기 때문에)

ViewModel은 액티비티 혹은 프래그먼트와 다른 생명주기를 가지게 된다.

ㄴ> ViewModel 생명 주기가 더 길다

ㄴ> 소유자 활동이 완료되면 (finish 메서드가 호출됐을 때 혹은 사용자가 직접 뒤로 가기 버튼을 눌러 액티비티를 종료했을 때 등)

프레임워크는 리소스를 정리할 수 있도록 onCleared() 메서드를 호출하여 ViewModel 소멸된다.

 

 

 


0. ViewModel 설정

dependencies {
    def lifecycle_version = "2.3.0"
    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
}

ㄴ> LiveData는 일반적으로 ViewModel 내에 존재하여 같이 기재함.

 

 


1. ViewModel 클래스 생성

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() {
        // Do an asynchronous operation to fetch users.
    }
}

ㄴ> LiveData는 일반적으로 ViewModel 내에 존재한다.

 


 

 

2. ViewModel 객체 생성

ㄴ> 일반적으로 액티비티 onCreate()메서드를 처음 호출할 때 ViewModel을 요청

더보기

 시스템은 활동 기간 내내(예: 기기 화면이 회전될 때) onCreate() 메서드를 여러 번 호출할 수 있습니다. ViewModel이 처음 요청되었을 때부터 활동이 끝나고 폐기될 때까지 ViewModel은 존재합니다.

ㄴ> ViewModel을 각각 다른 소유자가 생성하면 이는 별개의 메모리 공간을 사용하는 다른 객체가 된다.

ㄴ> 하나의 액티비티를 소유자로 지정해 사용하면 같은 ViewModel을 공유할 수 있다. = 데이터 공유 가능 -> Fragment간 데이터 공유

 


2-1. ViewModelProvider 로 생성하는 법

class MainActivity : AppCompatActivity() {
 
    private lateinit var userViewModel: UserViewModel
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // ViewModel 생성
        userViewModel = ViewModelProvider(this).get(UserViewModel::class.java)

        // ViewModel 내 LiveData 관찰 
        userViewModel.user.observe(this, Observer {
            // UI업데이트
            binding.textViewHeight.text = it.toString()
        })
    }
}

ViewModelProvider(this).get(UserViewModel::class.java)

ㄴ>  'this'는 owner를 의미 (owner가 'ViewModelStore(ViewModel 객체가 HashMap 구조로 저장되는 곳)'을 소유한다.)

ㄴ> 그러므로 get() 에 전달되는 'UserViewModel::class.java'는 ViewModel객체를 찾아오기 위한 Key값으로 쓰인다.

더보기

+) 만약 Key에 해당하는 Value가 없으면 생성하고 가져온다.

그래서 처음 뷰 모델 객체를 처음 만드는데도 set 따위가 아니라 get을 쓰는 것이며

이 ViewModelStore를 소유하고 있는 주체가 MainActivity라는 것을 알려주는 것이다.

 


 

2-2. activity-ktx 'by viewModels()' Kotlin 위임 프로퍼티로 생성

ㄴ> 데이터를 공유할 것이 아니라면 ⓑ 방법이 더 간결하여 편리

class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
    
        // ViewModel 생성 : activity-ktx에서 'by viewModels()' Kotlin 위임 프로퍼티를 사용하여 생성함
        val model: MyViewModel by viewModels()
        
        // ViewModel 내 LiveData 관찰 
        model.getUsers().observe(this, Observer<List<User>>{ users ->
            // update UI
        })
    }
}

 

 

 


 

ViewModel의 여러 사용처

1) 프래그먼트 간 데이터 공유

class SharedViewModel : ViewModel() {
    val selected = MutableLiveData<Item>()

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

class ListFragment : 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 ->
            // Update the UI
        }
    }
}

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 ->
            // Update the UI
        })
    }
}

ㄴ> by activityViewModels() : 두 프래그먼트는 모두 자신이 포함된 액티비티를 검색합니다.

그러면 각 프래그먼트는 ViewModelProvider를 가져올 때 이 액티비티으로 범위가 지정된 동일한 SharedViewModel 인스턴스를 받아 프래그먼트 간 데이터 공유가 가능하다.

 

ㄴ> 이 접근 방법의 이점

더보기
  • 액티비티는 아무것도 할 필요가 없거나 이 커뮤니케이션에 관해 어떤 것도 알 필요가 없습니다.
  • 프래그먼트는 SharedViewModel 계약 외에 서로 알 필요가 없습니다. 프래그먼트 중 하나가 사라져도 다른 프래그먼트는 계속 평소대로 작동합니다.
  • 각 프래그먼트는 자체 수명 주기가 있으며, 다른 프래그먼트 수명 주기의 영향을 받지 않습니다. 한 프래그먼트가 다른 프래그먼트를 대체해도, UI는 아무 문제 없이 계속 작동합니다.

 

 


2) ViewModel로 로더 대체하기

ㄴ> ViewModel + Room + LiveData와 함께 작업하여 로더를 대체합니다. 

(로더클래스 중 'CursorLoader'는 앱 UI의 데이터와 데이터베이스 간의 동기화를 유지하는 데 자주 사용됩니다.)

ㄴ> ViewModel 은 기기 설정이 변경되어도 데이터가 유지되도록 보장합니다. 

        데이터베이스가 변경되면 Room 에서 LiveData에 변경을 알리고,

        알림을 받은 LiveData는 수정된 데이터로 UI를 업데이트합니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


[출처]

- https://developer.android.com/topic/libraries/architecture/viewmodel

- https://todaycode.tistory.com/33?category=979455 

 

 

반응형