[Android] ViewModel

💡AAC의 ViewModel에 대하여 학습한 내용을 기록하였습니다.

 

ViewModel

  • ViewModel은 안드로이드 아키텍처 컴포넌트(AAC)의 일부로 UI 관련 데이터를 저장하고 관리하는 역할을 합니다.
  • 화면 회전 같은 구성 변경이 발생해도 데이터를 유지할 수 있으며, 사용자에게 지속적으로 일관된 데이터를 보여줄 때 유용합니다.

ViewModel 개요  |  Android Developers

 

ViewModel 개요  |  Android Developers

ViewModel을 사용하면 수명 주기를 인식하는 방식으로 UI 데이터를 관리할 수 있습니다.

developer.android.com

 

특징

  • Activity나 Fragment 생명주기와 독립적으로 데이터를 유지하기 위해 사용할 수 있습니다.
  • UI 데이터와 비즈니스 로직을 처리하며, 액티비티나 프래그먼트가 재생생되더라도 데이터가 손실되지 않도록 도와줍니다.

AAC?

  • Android Architecture Components
  • 구글에서 제작한 안드로이드 앱을 좀 더 쉽고 견고하게 개발할 수 있도록 돕는 라이브러리입니다.
  • AAC를 활용하면 테스트와 유지보수가 쉬운 앱을 디자인할 수 있습니다.

ViewModelStoreOwner

  • ViewModelStoreOwner는 ViewModel을 저장하고 관리하는 역할을 하는 안드로이드 인터페이스입니다.
  • ViewModelStoreOwner는 ViewModelStore를 소유하고, ViewModel을 생명주기에 따라 저장하거나 공유할 수 있는 기능을 제공합니다.
  • ViewModel을 인스턴스화할 때는 ViewModelStoreOwner 인터페이스를 구현하는 객체를 전달합니다.

역할

  • ViewModel 생명주기 관리
  • ViewModel 재사용 & 공유

ViewModelStoreOwner와 Navigation의 통합

  • ViewModelStoreOwner는 Navigation 구성 요소와 통합하여 정교한 데이터 관리를 지원합니다.
  • Navigation은 NavController와 NavHostFragment를 사용해 화면 전환을 관리하는데, 각 Destination이 고유한 ViewModelStoreOwner를 가질 수 있습니다.
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController

val viewModel = ViewModelProvider(navController.getViewModelStoreOwner(R.id.navigation_graph)).get(MyViewModel::class.java)
  • 위 코드를 통해 NavController와 함께 특정 navigation graph에 대해 ViewModel을 공유할 수 있도록 합니다.

ViewModelProvider

  • ViewModelProvider는 안드로이드에서 ViewModel 인스턴스를 생성하고 관리하는 클래스입니다.
  • ViewModel을 효율적으로 관리하고, Activity 또는 Fragment의 생명 주기와 연결하여 적절한 시점에 ViewModel을 제공하고 재사용할 수 있게 합니다.

ViewModelProvider 기본 사용

  • 기본 생성자를 가진 ViewModel을 Activity나 Fragment에서 사용할 수 있습니다.
class MainActivity : AppCompatActivity() {

    private lateinit var viewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // ViewModelProvider를 통해 ViewModel 가져오기
        viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
    }
}
  • ViewModelProvider(this)를 통해 생명 주기에 연결된 ViewModel을 생성 및 관리합니다.

Factory를 사용한 ViewModel 생성

  • ViewModel이 의존성을 필요로하는 경우 기본 생성자가 아닌 파라미터 생성자를 사용합니다.
  • ViewModelProvider.Factory로 이를 해결할 수 있습니다.
class MyViewModelFactory(private val repository: MyRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
            return MyViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

class MainActivity : AppCompatActivity() {

    private lateinit var viewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val repository = MyRepository()
        val factory = MyViewModelFactory(repository)

        // ViewModelProvider와 Factory를 통해 ViewModel 가져오기
        viewModel = ViewModelProvider(this, factory).get(MyViewModel::class.java)
    }
}

생명 주기와의 관계

  • ViewModelProvider는 ViewModelStoreOwner와 함께 사용되며, 이를 통해 ViewModel의 생명 주기가 관리됩니다.

ViewModel LifeCycle

  • 뷰모델은 액티비티나 프래그먼트의 생명주기와 밀접한 관계를 가지며, 그 수명 주기 동안 안전하게 데이터를 관리합니다.

ViewModel 개요  |  Android Developers

 

ViewModel 개요  |  Android Developers

ViewModel을 사용하면 수명 주기를 인식하는 방식으로 UI 데이터를 관리할 수 있습니다.

developer.android.com

생성 시점

  • Activity 또는 Fragment가 처음 생셩될 때 ViewModelProvider에 의해 생성됩니다.
  • Activity나 Fragment와 연결된 생명 주기를 따르지만, 독립적으로 유지됩니다.

유지 시점

  • ViewModel은 구성 변경이 발생해도 그대로 유지되며, 데이터가 초기화 되지 않습니다.

소멸 시점

  • Activity, Fragment의 생명 주기와 연결되어 있지만, 소멸 시점은 더 늦습니다.
    • Activity가 완전히 종료되었을 때, Activity가 재생성되지 않는 경우
    • Fragment가 완전히 제거되었을 때, Fragment가 재생성되지 않는 경우

onCleared()

  • ViewModel이 메모리에서 소멸되기 전에 호출됩니다.
  • 이를 활용하면 리소스를 해제하거나 정리 작업을 진행할 수 있습니다.
class MyViewModel : ViewModel() {
    override fun onCleared() {
        super.onCleared()
        // 리소스 정리, 데이터 저장 등 작업을 수행
    }
}

Configuration Change

  • 구성 변경 시 onDestory()가 호출되어도 ViewModel을 소멸되지 않습니다.
  • 액티비티 내에서 fiinish()를 호출하거나, 사용자가 액티비티나 프래그먼트를 닫을 때 소멸됩니다.

onSaveInstanceState(), onRestoreInstanceState()

  • 액티비티에서 onSaveInstanceState(), onRestoreInstanceState()를 통해서 데이터를 저장해두고 다시 전달받도록 합니다.
  • 이 방식은 데이터의 형태를 제한하고, 많은 양의 데이터를 저장하기에는 제한이 있으므로 ViewModel을 활용해서 이를 해결할 수 있습니다.

ViewModel과 Context

  • ViewModel은 액티비티나 프래그먼트가 파괴된 후에도 일정 시간 동안 메모리에 남아있을 수 있습니다.
  • 만약 ViewModel에 Context를 참조하게 되면 관련된 구성이 파괴되어도 그 객체들이 메모리에서 해제되지 않고 메모리 누수가 발생할 수 있습니다.

Application Context, Activity Context

  • Context가 꼭 필요한 경우에 ActivityContext가 아닌 ApplicationContext를 사용하는 것이 적합합니다.
  • 이는 앱의 생명주기 동안 지속되므로 메모리 누수의 위험이 없고, ViewModel의 생명주기와 잘 맞습니다.
  • 하지만 ApplicationContext는 리소스에 접근하거나 데이터베이스 작업을 수행할 때 사용할 수 있지만, UI 관련 작업을 처리하는 데 적합하지 않습니다.

AndroidViewModel 활용

  • AndroidViewModel은 ViewModel의 서브클래스로 Application Context를 사용할 수 있도록 도와줍니다.
  • AndroidViewModel를 상속하면 생성자에서 ApplicationContext에 접근할 수 있습니다.
  • 이는 Activity나 Fragment의 생명주기에 무관하게 Context를 안전하게 사용할 수 있습니다.
class MyViewModel(application: Application) : AndroidViewModel(application) {

    // Application Context를 가져와서 사용
    private val context: Context = getApplication<Application>().applicationContext

    fun doSomethingWithContext() {
        // Application Context 사용
        val sharedPreferences = context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
    }
}

Context 전달

  • ViewModel에서 Context가 필요한 경우만 전달하는 방법입니다.
  • Context가 지속적으로 유지되지 않고 필요한 순간에만 사용됩니다.
class MyViewModel : ViewModel() {

    fun performActionWithContext(context: Context) {
        // 필요한 순간에만 Context 사용
        val sharedPreferences = context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
    }
}
  • 이 방식은 메모리 누수를 방지하며, ViewModel이 Context에 직접 의존하지 않도록 만듭니다.

Repository 패턴 활용

  • Repository 패턴을 사용하여 Context가 필요한 작업을 Repository에서 처리하도록 분리할 수 있습니다.
  • ViewModel은 Repository와 통신하고, Context 관련 작업은 Repository 내부에서만 이루어집니다.
class MyRepository(private val context: Context) {
    fun saveDataToPreferences(data: String) {
        val sharedPreferences = context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
        sharedPreferences.edit().putString("key", data).apply()
    }
}

class MyViewModel(private val repository: MyRepository) : ViewModel() {
    fun saveData(data: String) {
        repository.saveDataToPreferences(data)
    }
}
  • ViewModel이 Context에 직접 의존하지 않게 하며, ViewModel과 Context 관련 작업을 명확하게 분리 할 수 있습니다.