Android 에서 ViewModel 을 사용하면서, Process 종료 후 재시작할 때 ViewModel 의 상태 저장을 위해 state: SavedStateHandle 를 사용합니다.

class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }class MainFragment : Fragment() {
val vm: SavedStateViewModel by viewModels()
...
}

위 코드와 같이 생성자에 추가하면 되며, 이를 위해 AbstractSavedStateViewModelFactory 가 기본적으로 구현되어 있습니다. 단, androidx.fragment 1.2 혹은 androidx.activity 1.1 이상이 추가되어야 편하게 사용할 수 있습니다.

Activity 및 ViewModel 동작 확인을 위해 아래와 같이 작업합니다.

class MainActivity : AppCompatActivity() {    private val viewModel: MainActivityViewModel by viewModels()    override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

if (savedInstanceState == null) {
// Initialization code for first creation.
supportFragmentManager
.beginTransaction()
.replace(R.id.container, MainFragment.newInstance())
.commit()
}
findViewById<Button>(R.id.countButton)?.let { view ->
viewModel.count.observe(this) { count ->
view.text = "$count"
}
view.setOnClickListener { viewModel.addCount() }
}
findViewById<Button>(R.id.countStateButton)?.let { view ->
viewModel.countState.observe(this) { count ->
view.text = "$count"
}
view.setOnClickListener { viewModel.addCountState() }
}
}
}
class MainActivityViewModel(state: SavedStateHandle) : ViewModel() { private fun <T> SavedStateHandle.getLiveData(
key: StateKey,
initial: T
) = getLiveData(key.value, initial)
private enum class StateKey(val value: String) {
Count("count")
}
private val countStateLiveData =
state.getLiveData(StateKey.Count, 0)
val countState: LiveData<Int> = countStateLiveData
fun addCountState() = countStateLiveData.postValue(
countStateLiveData.value as Int + 1
)
private val countLiveData = MutableLiveData(0)
val count: LiveData<Int> = countLiveData
fun addCount() = countLiveData.postValue(
countLiveData.value as Int + 1
)
}

ViewModel 의 val countState 는 SavedStateHandle 로 부터 값을 가져오며, val count 는 ViewModel 에 속한 LiveData 를 통해 값을 가져옵니다. Android Developer Option 의 Process 갯수 제한 혹은 활동 제한을 통해 테스트를 해보면 Process 재시작 시 countState 는 복원되고, count 는 복원되지 않는 것을 확인 할 수 있습니다.

Fragment 의 경우에도, ViewModel 관련 구현은 동일합니다.

class MainFragment : Fragment() {    companion object {
fun newInstance() =
MainFragment().apply {
arguments = Bundle().apply {
// put arguments.
}
}
}
private val viewModel: MainFragmentViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) {
Log.e("TEST", "MainFragment.onCreate")
super.onCreate(savedInstanceState)
when (savedInstanceState) {
null -> {
// Initialization code for first creation
// in onCreate.
}
else -> {
// Initialization code for re-creation in onCreate.
}
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? =
inflater.inflate(R.layout.fragment_main, container, false)
.apply {
if (savedInstanceState == null) {
// Initialization code for first creation
// in onCreateView.
childFragmentManager
.beginTransaction()
.replace(
R.id.container,
ChildFragment.newInstance())
.commit()
}

findViewById<Button>(R.id.countButton)
?.let { view ->
viewModel.count
.observe(viewLifecycleOwner) { count ->
view.text = "$count"
}
view.setOnClickListener { viewModel.addCount() }
}
findViewById<Button>(R.id.countStateButton)
?.let { view ->
viewModel.countState
.observe(viewLifecycleOwner) { count ->
view.text = "$count"
}
view.setOnClickListener {
viewModel.addCountState() }
}
}
}

ViewModel 구현은 동일하며, Process 재시작 시 countState 는 복원되고, count 는 복원되지 않는 것을 확인 할 수 있습니다.

Activity 및 Fragment 의 SavedInstanceState 를 사용하는 것 보다는 Lifecycle 신경을 안쓰고 상태를 복원할 수 있습니다. 하지만 Bundle 을 사용하는 조건은 역시 동일하기 때문에 제약 조건은 여전히 존재합니다. Bundle 의 제약을 벗어나기 위해서는 Room 과 같은 Persistent storage 를 사용해야 합니다.

결국 선택의 영역으로 오게 되는데요.

  1. Process 종료 후 재시작할 때 Room 만 사용합니다. 다만, Remote Server 의 Data 와 Client Data 의 동기화 기능이 필수적으로 요구됩니다.
  2. Bundle 로 저장하기 힘든 부분만 Room 을 사용하여 저장하고, 그 외는 SavedStateHandle 을 사용합니다. 하지만, 가장 중요한 RecyclerView 의 Item 들이 대부분 Room 에 저장해야 하기 때문에 1번과 난이도 차이가 그리 크지 않습니다. 하지만 Scroll Position 등 Bundle 로 기본적으로 State 를 반환하는 함수들이 꽤 있어서 1번 보다는 편한 편입니다.
  3. Process 종료 후 재시작 된다는 건, 저사양 기기에서 오랫동안 Application 을 사용하지 않은 경우 발생하기 때문에, 굳이 상태 저장을 할 필요가 없을 수 있습니다. 이 경우, Activity 및 Fragment 상태는 유지되기 때문에, Process 재시작 시 앱 초기 화면으로 강제로 이동시킬 필요가 있습니다.

--

--