💡AAC의 ViewModel에 대하여 학습한 내용을 기록하였습니다.
ViewModel
- ViewModel은 안드로이드 아키텍처 컴포넌트(AAC)의 일부로 UI 관련 데이터를 저장하고 관리하는 역할을 합니다.
- 화면 회전 같은 구성 변경이 발생해도 데이터를 유지할 수 있으며, 사용자에게 지속적으로 일관된 데이터를 보여줄 때 유용합니다.
ViewModel 개요 | Android Developers
특징
- 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
생성 시점
- 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 관련 작업을 명확하게 분리 할 수 있습니다.