개발일지

Android in A..Z - Lifecycle (ViewModel) 본문

Android (안드로이드)/Lifecycle

Android in A..Z - Lifecycle (ViewModel)

강태종 2021. 2. 1. 17:33

ViewModel

Android Jetpack의 구성요소이며 UI관련 데이터를 관리하도록 설계되었습니다. UI관련 Data를 Activity, Fragment와 분리시켜 관리하면서 많은 이점을 얻을 수 있습니다.

Jetpack


장점

  • Android에서 Activity와 Fragment의 수명주기를 관리하기 때문에 Android에서 UI 컨트롤러를 제거하거나 다시 만드는 경우 데이터가 삭제되지만 ViewModel은 독립된 수명주기를 가지기 떼문에 데이터를 유지할 수 있습니다.
  • UI관련 Data를 비동기적인 호출로 받아올 때 메모리 누수를 신경쓰거나, 비동기 호출을 관리하는 코드가 필요하거나 UI 컨트롤러가 변경될 경우 이미 호출한 코드를 다시 호출해야하는 경우가 생길 수 있지만 ViewModel을 재사용하면서 이러한 불편함을 해소할 수 있습니다.
  • UI 컨트롤러 로직과 데이터 관련 코드를 분리하면서 유지보수를 쉽게 할 수 있습니다.

ViewModel 생명주기


Dependency

def lifecycle_version = "2.2.0"

// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// Navigation
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2'

예제

화면 회전시 Android에서 Activity를 재생성하기 때문에 UI관련 데이터가 삭제되지만 ViewModel을 사용시 데이터를 유지할 수 있다.

 

MainActivity

class MainActivity : BaseActivity<ActivityMainBinding>(R.layout.activity_main) {
//    생성자가 필요한 ViewModel 사용하는 코드. (ViewModelProvider.Factory 사용하면 된다.)
//    private val model by viewModels<ChronometerViewModel> { ChronometerViewModelFactory(3000L) }

    private val model by viewModels<ChronometerViewModel>()
    private val recordAdapter by lazy { RecordAdapter() }

    private fun onResetChronometer() {
        model.state.value = ChronometerViewModel.State.RESET
        model.sumOfTickTime = 0L
        binding.chronometer.base = SystemClock.elapsedRealtime()

        val count = model.recordList.size
        model.recordList.clear()
        recordAdapter.notifyItemRangeRemoved(0, count)
    }

    private fun onStartChronometer() {
        model.state.value = ChronometerViewModel.State.START

        val tickTime = SystemClock.elapsedRealtime()
        model.lastTickTime = tickTime
        binding.chronometer.base = SystemClock.elapsedRealtime() - model.sumOfTickTime

        onRunChronometer()
    }

    private fun onRunChronometer() {
        model.state.value = ChronometerViewModel.State.RUN
    }

    private fun onResumeChronometer() {
        model.state.value = ChronometerViewModel.State.RESUME

        val tickTime = SystemClock.elapsedRealtime()
        model.lastTickTime = tickTime
        binding.chronometer.base = SystemClock.elapsedRealtime() - model.sumOfTickTime

        onRunChronometer()
    }

    private fun onStopChronometer() {
        model.state.value = ChronometerViewModel.State.STOP
        binding.chronometer.base = SystemClock.elapsedRealtime() - model.sumOfTickTime
    }

    override fun init() {
        super.init()
        initChronometer()
        initViewModel()
        initStartButton()
        initStopButton()
        initResumeButton()
        initResetButton()
        initRecordButton()
        initRecyclerView()
    }

    private fun initViewModel() {
        with(model) {
            state.observe(this@MainActivity) {
                binding.state = it
                when(it ?: ChronometerViewModel.State.RESET) {
                    ChronometerViewModel.State.RUN -> {
                        binding.chronometer.start()
                    }
                    ChronometerViewModel.State.RESET -> {
                        binding.chronometer.stop()
                    }
                    ChronometerViewModel.State.STOP -> {
                        binding.chronometer.stop()
                    }
                    else -> {

                    }
                }
            }
        }
    }

    private fun initChronometer() {
        with(binding.chronometer) {
            base = SystemClock.elapsedRealtime() - model.sumOfTickTime
            setOnChronometerTickListener {
                val tickTime = SystemClock.elapsedRealtime()
                model.sumOfTickTime += tickTime - model.lastTickTime
                model.lastTickTime = tickTime
            }
        }
    }

    private fun initResetButton() {
        binding.setOnReset {
            onResetChronometer()
        }
    }

    private fun initStartButton() {
        binding.setOnStart {
            onStartChronometer()
        }
    }

    private fun initStopButton() {
        binding.setOnStop {
            onStopChronometer()
        }
    }

    private fun initResumeButton() {
        binding.setOnResume {
            onResumeChronometer()
        }
    }

    private fun initRecordButton() {
        binding.setOnRecord {
            with(model) {
                recordList.add(Record(recordList.size.toLong(), sumOfTickTime))
                recordAdapter.notifyItemInserted(recordList.size - 1)
            }
        }
    }

    private fun initRecyclerView() {
        with(binding.recyclerView) {
            adapter = recordAdapter
            recordAdapter.submitList(model.recordList)
        }
    }
}

ChronometerViewModel

class ChronometerViewModel(initialTime: Long = 0L) : ViewModel() {
    var sumOfTickTime = initialTime
    var lastTickTime = 0L

    val recordList by lazy { ArrayList<Record>() }

    val state by lazy { MutableLiveData(State.RESET) }

    enum class State {
        RESET, START, RUN, STOP, RESUME
    }
}

ChronometerViewModelFactory

class ChronometerViewModelFactory(private val initialTime: Long) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return ChronometerViewModel(initialTime) as T
    }
}

Git (예제코드)

github.com/KangTaeJong98/Example/tree/main/Android/LifeCycle

 

KangTaeJong98/Example

My Example Code. Contribute to KangTaeJong98/Example development by creating an account on GitHub.

github.com

 

Comments