본문 바로가기

안드로이드/JetPack

[안드로이드] 5. ViewModel을 이용하여 UI와 로직을 분리

ViewModel

UI와 로직을 분리하기 위한 라이브러리이다. 이렇게 구현하면 앱의 구조가 더욱 향상된다. 또한 안드로이드에서 화면을 회전할 경우 액티비티 모두 초기화되고 파괴된다(데이터가 사라짐). 이러한 것을 방지하기 위해 사용하기도 한다. 이는 ViewModel의 수명은 액티비티가 완전히 파괴되기 전까지 메모리에 남아있기 때문에 가능하다.

ViewModel을 위한 구성 추가 build.gradle(:app)

android {
    ...
    kotlinOptions {
        jvmTarget ="1.8"
    }

}


dependencies{
	...
    implementation 'androidx.activity:activity-ktx:1.1.0'
    ...
 }

 

그리고 데이터와 관련된 작업을 ViewModel로 이전시킨다.

 

MainViewModel.kt

class MainViewModel(application: Application): AndroidViewModel(application) {
	//MainActvity에서 가져온 db
    val db = Room.databaseBuilder(
        application,
        AppDatabase::class.java, "database-name"
    ).build()

	//MainActvity에서 db접근을 하기 위한 추상화
    fun getAll() : LiveData<List<Todo>>{
        return db.todoDao().getAll()
    }

    suspend fun insert(todo: Todo){
        db.todoDao().insert(todo)
    }
}

MainActivity.kt

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

        ViewModel.getAll().observe(this, Observer { todos ->
            result_textView.text = todos.toString()
        }) //getAll을 할 경우 todos로 값이 들어온다.

        add_button.setOnClickListener {
            lifecycleScope.launch(Dispatchers.IO) {
                ViewModel.insert(Todo(todo_edit.text.toString()))
            }
        }
    }
}

추가적으로 비동기 작업을 강제하기 위해 코루틴 내에서만 작업을 허용하는 suspend를 사용하였다.


안드로이드 공식 가이드의 ViewModel 예제

 

뷰모델은 액티비티의 데이터 유지 문제, UI 컨트롤러(액티비티, 프래그먼트)가 무거워지는 문제를 해결한다. 아래처럼 뷰모델은 UI를 업데이트 하는데 필요한 데이터를 가지고 있다. 그리고 액티비티에서 해당 데이터를 접근한다.

 class MyViewModel : ViewModel() {
        private val users: MutableLiveData<List<User>> by lazy {
            MutableLiveData().also {
                loadUsers()
            }
        }

        fun getUsers(): LiveData<List<User>> {
            return users
        }

        private fun loadUsers() {
            //유저 정보를 가져오기 위한 비동기 작업
        }
    }
    class MyActivity : AppCompatActivity() {
		val model: MyViewModel by viewModels()
        override fun onCreate(savedInstanceState: Bundle?) {
            model.getUsers().observe(this, Observer<List<User>>{ users ->
                // update UI
            })
        }
    }
    

 

뷰모델을 이용하여 프래그먼트간의 데이터 공유

액티비티에 속한 둘 이상의 프래그먼트가 동일한 데이터를 사용하는 경우는 매우 일반적이다. 아래는 사용자 목록에서 항목을 선택하는 MasterFragment와 선택된 항목을 자세히 보여주는 DetailFragment이다.

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

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

    class MasterFragment : 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
            })
        }
    }