Skip to content

Latest commit

ย 

History

History
443 lines (335 loc) ยท 20.8 KB

How-to-MVVM.md

File metadata and controls

443 lines (335 loc) ยท 20.8 KB

MVVM, ์‹ค์ œ๋กœ ์ ์šฉํ•ด๋ณด์ž!

MVVM์„ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—๋„ ์ ์šฉํ•ด๋ด…์‹œ๋‹น~~

์ž‘์„ฑ์ž : ์ด์ข…ํ˜„

Present Time : 2018โ€“10-03-WED Supplement Present Time: 2018-10-24 WED

0. ๊ฐœ์š”

0-1. MVVM์ด๋ž€?

MVVM์ด๋ž€ Model-View-ViewModel์œผ๋กœ, ๊ตฌ๊ธ€ Android Architecture Component(์ดํ•˜ AAC)์˜ ์ง€์›์œผ๋กœ ์ธํ•ด ์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ ์ ์  ๋ถ€์ƒํ•˜๊ณ ์žˆ๋Š” ๋””์ž์ธ ํŒจํ„ด์ž…๋‹ˆ๋‹ค.
๊ฐ ์š”์†Œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ๋ถ€๋ถ„์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

Model

  • ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์ด๊ณ  ๋„คํŠธ์›Œํฌ, ๋‚ด๋ถ€ DB ๋“ฑ์œผ๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

View

  • Activity, Fragment ๋“ฑ ์ „๋ฐ˜์ ์ธ ๋ชจ๋“  View๋ฅผ ๋œปํ•ฉ๋‹ˆ๋‹ค.
  • ViewModel์„ ๊ด€์ธกํ•˜๋ฉฐ, ๋ณ€ํ™”๋œ ViewModel์˜ ๋ฐ์ดํ„ฐ๋ฅผ View์— ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.

ViewModel

  • View์— ๋Œ€ํ•œ Model์ž…๋‹ˆ๋‹ค.
  • Model์—์„œ ์ œ๊ณต๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ , ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์„๋•Œ Event๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
  • MVP ํŒจํ„ด๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ 1:N ๊ตฌ์กฐ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ViewModel๊ณผ View๋Š” DataBinding์œผ๋กœ ๊ธด๋ฐ€ํ•˜๊ฒŒ ๊ฒฐํ•ฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

Databinding์— ๊ด€ํ•œ ๋‚ด์šฉ์€ ์ด ๊ธ€์„ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”.

1. ๊ตฌํ˜„ํ•ด๋ณด๊ธฐ

์ง€๋‚œ๋ฒˆ๊ณผ ๊ฐ™์ด, ๋‘ ๊ฐœ์˜ EditText์—์„œ ํ…์ŠคํŠธ๋ฅผ ๋ฐ›์•„์™€ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•ด์ฃผ๋Š” ๊ฐ„๋‹จํ•œ ๊ณ„์‚ฐ๊ธฐ๋ฅผ ๋งŒ๋“ค์–ด๋ด…์‹œ๋‹ค!

1-1. ViewModel, Layout ์ž‘์„ฑํ•˜๊ธฐ

๋ ˆ์ด์•„์›ƒ์—๋Š” 2๊ฐœ์˜ EditText, 4๊ฐœ์˜ ๊ณ„์‚ฐ ๋ฒ„ํŠผ, ๊ฒฐ๊ณผ๋ฅผ ๋„์›Œ์ค„ ํ…์ŠคํŠธ๋ทฐ๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ์ด์— ์ƒ์‘ํ•˜๋Š” ๋ทฐ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด๋ด…์‹œ๋‹ค.

class MainViewModel {
    val firstNum = ObservableField<String>()
    val secondNum = ObservableField<String>()
    val result = ObservableField<String>()

    fun calc(op: Char) {
        result.set(when (op) {
            '+' -> (firstNum.get()!!.toInt() + secondNum.get()!!.toInt()).toString()
            '-' -> (firstNum.get()!!.toInt() - secondNum.get()!!.toInt()).toString()
            '*' -> (firstNum.get()!!.toInt() * secondNum.get()!!.toInt()).toString()
            '/' -> (firstNum.get()!!.toInt() / secondNum.get()!!.toInt()).toString()
            else -> ""
        })
    }
}
  • ObservableField๋ฅผ ์ด์šฉํ•˜์—ฌ xml์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”์ธ๋”ฉ ํ•  ์ˆ˜ ์žˆ๋Š” ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

  • calc ๋ฉ”์†Œ๋“œ๋กœ firstNum๊ณผ secondNum์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€์„œ ์—ฐ์‚ฐ์„ ํ•œ ํ›„, result์— ๋„ฃ์Šต๋‹ˆ๋‹ค.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewModel"
            type="com.example.dwg76.databinding_example.MainViewModel" />
    </data>

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <EditText
            android:id="@+id/firstNum"                            
            android:text="@={viewModel.firstNum}" />

        <EditText
            android:id="@+id/secondNum"
            android:text="@={viewModel.secondNum}" />

        <Button
            android:id="@+id/plus"
            android:onClick="@{() -> viewModel.calc('+')}"
            android:text="๋”ํ•˜๊ธฐ" />

        <Button
            android:id="@+id/minus"
            android:onClick="@{() -> viewModel.calc('-')}"
            android:text="๋นผ๊ธฐ" />

        <Button
            android:id="@+id/divide"
            android:onClick="@{() -> viewModel.calc('/')}"
            android:text="๋‚˜๋ˆ„๊ธฐ"/>

        <Button
            android:id="@+id/multiple"
            android:onClick="@{() -> viewModel.calc('*')}"
            android:text="๊ณฑํ•˜๊ธฐ"/>

        <TextView
            android:id="@+id/result"
            android:text="@{viewModel.result}"/>
    </android.support.constraint.ConstraintLayout>
</layout>
  • Two-way Databinding์„ ์ด์šฉํ•˜์—ฌ ViewModel์˜ ObservableField๋“ค์„ EditText์— ๋ฐ”์ธ๋”ฉํ•ด์ค๋‹ˆ๋‹ค.
  • ๊ฐ Button์— onClick์ด ์‹คํ–‰๋  ์‹œ ViewModel์˜ calc ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ํ•ด ๋‘์—ˆ์Šต๋‹ˆ๋‹ค.
    1. ๊ทธ๋Ÿฌ๋ฉด ViewModel์— ์ด๋ฒคํŠธ๊ฐ€ ๋“ค์–ด์˜ค๋ฉด ViewModel์€ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ , ViewModel์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.
    2. View๋Š” ViewModel์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€์ธกํ•˜๊ณ  ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ”๋€๋‹ค๋ฉด View์— ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.

Activity ์ž‘์„ฑํ•˜๊ธฐ

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        val viewModel = MainViewModel()
        binding.viewModel = viewModel
    }
}
  • ๊ธฐ์กด setContentView ๋ฅผ ์ง€์šฐ๊ณ , DatabindingUtil์„ ์ด์šฉํ•˜์—ฌ ๋‹ค์‹œ setContentView๋ฅผ ํ•ด์ค๋‹ˆ๋‹ค.
  • ActivityMainBinding์€ ๋ ˆ์ด์•„์›ƒ์˜ ๋ฃจํŠธ ํƒœ๊ทธ๋ฅผ layout์œผ๋กœ ๋ฐ”๊พธ๋ฉด ์ด๋ฆ„์— ๋งž์ถฐ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.
  • binding๊ฐ์ฒด์— viewModel์„ ํ• ๋‹นํ•ด์ฃผ๋ฉด xml์—์„œ viewModel์— ์ ‘๊ทผ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

2-0. Android Architecture Component

๊ทผ๋ฐ ์ด๊ฒŒ ํ™”๋ฉด์„ ๋Œ๋ฆฌ๋ฉด ๋ฐ์ดํ„ฐ๋“ค์ด ์‚ฌ๋ผ์ ธ์š”

onCreate๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ViewModel์ด ์ƒ์„ฑ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๊ตฌ๊ธ€์€ ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ƒ๋ช…์ฃผ๊ธฐ์— ์ตœ์ ํ™”๋œ ViewModel๊ณผ LiveData๋ฅผ ๋‚ด๋†“์Šต๋‹ˆ๋‹ค.

๊ธฐ์กด ์ฝ”๋“œ๋ฅผ ์กฐ๊ธˆ ๋ฆฌํŒฉํ† ๋ง๋งŒ ํ•˜๋ฉด ํ”„๋กœ๊ทธ๋ž˜๋จธ๋Š” ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ํž˜๋“ค๊ฒŒ ๊ด€๋ฆฌํ•  ํ•„์š”๊ฐ€ ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค. ๋ฌผ๋ก  ๊ธฐ์กด ๊ตฌ์กฐ๊ฐ€ MVVM ํ•œ์ •์ผ ๋•Œ ์ด์ง€๋งŒ์š”.

์ด ๊ธ€์ด AAC๋ฅผ ๋‹ค๋ฃจ๋Š” ๊ธ€์€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์—, ๋น„์ค‘์€ ์ ์ง€๋งŒ ViewModel๊ณผ LiveData๊ฐ€ ๋ฌด์—‡์ธ์ง€ ๊ฐ„๋žตํ•˜๊ฒŒ ์„ค๋ช…ํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

ViewModel

AAC์˜ ViewModel์€ ์ƒ๋ช…์ฃผ๊ธฐ์— ์ตœ์ ํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
์•„๊นŒ ๋งŒ๋“ค์—ˆ๋˜ ViewModel์€ View๊ฐ€ ๋ฉˆ์ถ”๋ฉด ViewModel์ด ์‚ฌ๋ผ์ง€๋Š” ๋ฐ˜๋ฉด, AAC์˜ ViewModel์€ Activity๊ฐ€ ๋๋‚  ๋•Œ ๊นŒ์ง€ ์‚ฌ๋ผ์ง€์ง€ ์•Š๊ณ , View์˜ ์ƒ๋ช…์ฃผ๊ธฐ์™€ ๋ณ„๊ฐœ๋กœ ํ˜๋Ÿฌ๊ฐ‘๋‹ˆ๋‹ค.

Illustrates the lifecycle of a ViewModel as an activity changes state.

์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋‚˜์š”?

class MainViewModel : ViewModel() {

}
  1. AAC์˜ ๋ทฐ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ViewModel ํด๋ž˜์Šค๋ฅผ ์ƒ์†๋ฐ›์Šต๋‹ˆ๋‹ค.
  2. View์—์„œ ViewModel์„ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ์—๋Š” ViewModelProviders๋ฅผ ์ด์šฉํ•˜์—ฌ ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค.
val viewModel = ViewModelProviders.of(this)[MainViewModel::class.java]

AAC์˜ ViewModel์„ ์‚ฌ์šฉํ•˜๋ฉด ์ƒ๋ช…์ฃผ๊ธฐ์— ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š๊ณ  ์•ˆ์ „ํ•œ ViewModel์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ViewModel์€ ViewModelProviders.of()์˜ ์ธ์ž๋กœ FragmentActivit ํ˜น์€ Fragment๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.

์ด ๋‘ ํด๋ž˜์Šค๋Š” LiceCycleOwner๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”๋ฐ, ViewModelProviders๋Š” LifeCycleOwner์— ๋”ฐ๋ผ ViewModel ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ค๋‹ˆ๋‹ค.

tl;dr

์ด๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ฐ™์€ ์•กํ‹ฐ๋น„ํ‹ฐ ์•„๋ž˜์— ์žˆ๋Š” ํ”„๋ž˜๊ทธ๋จผํŠธ๋“ค๋ผ๋ฆฌ ๋ฐ์ดํ„ฐ ๊ณต์œ ๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

Fragment์—์„œ getActivity()๋กœ ์•กํ‹ฐ๋น„ํ‹ฐ์˜LifeCycleOwner๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด ๊ฐ™์€ ์•กํ‹ฐ๋น„ํ‹ฐ ์•„๋ž˜์— ์žˆ๋Š” ๋‘๊ฐœ์˜ ํ”„๋ž˜๊ทธ๋จผํŠธ๊ฐ€ ๊ฐ™์€ ViewModel์„ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

LiveData

LiveData๋Š” ์ƒ๋ช…์ฃผ๊ธฐ์™€ ๋ฐ์ดํ„ฐ์˜ ๋ณ€๊ฒฝ์„ ์ธ์ง€ํ•  ์ˆ˜ ์žˆ๋Š” ๊ด€์ฐฐ์ด ๊ฐ€๋Šฅํ•œ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.

observe ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ LiveData๋ฅผ ๊ด€์ธกํ•  ์ˆ˜ ์žˆ๊ณ , ๊ฐ’์ด ๋ณ€ํ™”๋˜๋ฉด observe๊ฐ€ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

Start ์™€ Resume ์ƒํƒœ์ผ ๋•Œ observe๊ฐ€ ํ™œ์„ฑํ™”๋˜๊ณ , Destroyed ์ƒํƒœ๋กœ ๋“ค์–ด๊ฐˆ ๋•Œ ๊ด€์ฐฐ์„ ์ทจ์†Œํ•ฉ๋‹ˆ๋‹ค.

์ด๋กœ์จ, ๋ฉ”๋ชจ๋ฆฌ ๋ฆญ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

LiveData์™€ Databinding์„ ํ˜ผ์šฉํ•˜๋ ค๋ฉด

๋ณดํ†ต์˜ Databinding์„ ์‚ฌ์šฉํ•  ๋•Œ์—๋Š” ๋ฐ์ดํ„ฐ๋ฐ”์ธ๋”ฉ์—์„œ ์ง€์›ํ•˜๋Š” Observable ๊ณผ ๊ฐ™์€ ํด๋ž˜์Šค๋“ค์„ ์‚ฌ์šฉํ•ด์•ผํ–ˆ์ง€๋งŒ, Databinding ๊ฐ์ฒด์—์„œ setLifecycleOwner()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด Observable ๋Œ€์‹  LiveData๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ• ๋‹น์‹œํ‚ค๋Š” ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
binding.setLifecycleOwner(this)

2-1. AAC๋กœ ๋ฐ”๊ฟ”๋ณด์ž

๋ฐฉ๊ธˆ ์งฐ๋˜ ์ฝ”๋“œ์— AAC๋ฅผ ์ ์šฉํ•ด๋ด…์‹œ๋‹ค!

์ƒ๊ฐ๋ณด๋‹ค ๊ฐ„๋‹จํ•˜๊ฒŒ, ObservableField๋“ค์„ ๋ชจ๋‘ LiveData๋กœ ๋ฐ”๊พธ๊ณ , ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ๊ฐ์ฒด์˜ setLifeCycleOwnerํ•จ์ˆ˜ ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

class MainViewModel : ViewModel() {
    val firstNum = MutableLiveData<String>()
    val secondNum = MutableLiveData<String>()
    val result = MutableLiveData<String>()

    fun calc(op: Char) {
        Log.d("MainViewModel", "${firstNum.value}, ${secondNum.value}")
        result.value = when (op) {
            '+' -> (firstNum.value!!.toInt() + secondNum.value!!.toInt()).toString()
            '-' -> (firstNum.value!!.toInt() - secondNum.value!!.toInt()).toString()
            '*' -> (firstNum.value!!.toInt() * secondNum.value!!.toInt()).toString()
            '/' -> (firstNum.value!!.toInt() / secondNum.value!!.toInt()).toString()
            else -> ""
        }
        Log.d("MainViewModel", "${result.value}")
    }
}
  • MutableLiveData๋Š” ๋ณ€ํ•  ์ˆ˜ ์žˆ๋Š” LiveData๋กœ, ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜๋Š” ๋ฐ์ดํ„ฐ์ผ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ๋ณดํ†ต ViewModel ์•ˆ์— private๋กœ MutableLiveData๋ฅผ ์ƒ์„ฑํ•˜๊ณ , LiveData๋กœ View์— ๋ฐ์ดํ„ฐ๋ฅผ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        val viewModel = ViewModelProviders.of(this)[MainViewModel::class.java]
        binding.setLifecycleOwner(this)
        binding.viewModel = viewModel
    }
}

xml์€ ๋”ฐ๋กœ ๊ฑด๋“œ๋ฆด ํ•„์š” ์—†์ด ๋˜‘๊ฐ™์ด ๋‘์–ด๋„ ์ƒ๊ด€์ด ์—†์Šต๋‹ˆ๋‹ค. ๊ฐ„๋‹จํ•˜๊ฒŒ AAC๋ฅผ ์ ์šฉ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์ œ ์ƒ๋ช…์ฃผ๊ธฐ์—๋„ ์•ˆ์ „ํ•˜๋„ค์š”. ์™€!

3-1. ์‹ค์ œ๋กœ ์ ์šฉํ•˜๋ฉด ์–ด๋–จ๊นŒ์š”? with JustGo

JustGo๋Š” ํ•„์ž๊ฐ€ ์ง„ํ–‰ํ–ˆ๋˜ ํ”„๋กœ์ ํŠธ์ธ๋ฐ, AAC์™€ MVVM ํŒจํ„ด์„ ์ด์šฉํ•˜์—ฌ ๊ฐœ๋ฐœํ•˜์˜€์Šต๋‹ˆ๋‹ค. JustGo์™€ ํ•จ๊ป˜ MVVM ํŒจํ„ด์„ ์–ด๋–ป๊ฒŒ ์ ์šฉํ•ด์•ผํ• ์ง€ ์•Œ์•„๋ด…์‹œ๋‹ค. ์ „์ฒด ํ”„๋กœ์ ํŠธ๋Š” ์ด ๊ณณ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‚ฌ์ „ ์ƒํ™ฉ ์„ค๋ช…

  • JustGo๋Š” ๋ฌด์ž‘์œ„ ์—ฌํ–‰์ง€ ์„œ๋น„์Šค๋กœ, ๊ด€๊ด‘์ง€๊นŒ์ง€ ๊ฐ„๋‹จํ•œ ๋„ค๋น„๊ฒŒ์ด์…˜์„ ์ œ๊ณตํ•ด์ค๋‹ˆ๋‹ค.
  • ํ™”๋ฉด์—๋Š” ๊ฐ€์•ผ ํ•  ๋ชฉ์ ์ง€(ex. ๋ฒ•์›์‚ฌ๊ฑฐ๋ฆฌ๊นŒ์ง€ ๋„๋ณด๋กœ ์ด๋™)๋ฅผ ์•Œ๋ ค์ค๋‹ˆ๋‹ค.
  • ํ™”๋ฉด์—๋Š” ์ง€๋„๋ฅผ ๋„์›Œ์ฃผ๋ฉฐ, ์ง€๋„๋Š” ์„œ๋ฒ„์—์„œ ๊ฐ€์ ธ์˜จ ๋งˆ์ปค, ํด๋ฆฌ๋ผ์ธ ์ •๋ณด๋ฅผ ์•Œ๋ ค์ค๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž์˜ ์œ„์น˜ ์ •๋ณด๋ฅผ ๊ณ„์† ๋ฐ›์•„์˜ค๋ฉฐ, ๋ชฉ์ ์ง€์— ๋„์ฐฉํ•˜๋ฉด ๋‹ค์Œ ๋ชฉ์ ์ง€๋ฅผ ๋„์›Œ์ค๋‹ˆ๋‹ค.
  • ์ตœ์ข… ๊ด€๊ด‘์ง€์— ๋„์ฐฉํ•˜๋ฉด, ๊ทธ ์—ฌํ–‰์ง€ ์ •๋ณด๋ฅผ ์•Œ๋ ค์ฃผ๋Š” ์ƒˆ๋กœ์šด ์•กํ‹ฐ๋น„ํ‹ฐ๋ฅผ ๋„์›Œ์ค๋‹ˆ๋‹ค.

์œ„์™€ ๊ฐ™์€ ๊ฐ„๋‹จํ•œ ๋„ค๋น„๊ฒŒ์ด์…˜ ์ฝ”๋“œ๋ฅผ ๋ฆฌ๋ทฐํ•˜๋ฉฐ MVVM ํŒจํ„ด์ด ์–ด๋–ค ์‹์œผ๋กœ ์ง„ํ–‰๋˜๋Š”์ง€ ์•Œ์•„๋ด…์‹œ๋‹ค.

ViewModel

class NavigationViewModel : ViewModel() {
    private val _direction = MutableLiveData<ArrayList<DirectionModel.Point>>()
    private val _polyLine = MutableLiveData<String>()
    private val _transit = MutableLiveData<String>()
    private val _type = MutableLiveData<String>()   

    val direction get() = _direction as LiveData<ArrayList<DirectionModel.Point>>
    val polyLine get() = _polyLine as LiveData<String>   
    val transit get() = _transit as LiveData<String>
    val type get() = _type as LiveData<String>    
    val travelFinishEvent = SingleLiveEvent<Any>()
    
    var index = 0


    fun getNavigation(transport: String, lat: Double, lng: Double, desLat: Double, desLng: Double) {
        getDirection(transport, lat, lng, desLat, desLng) {
            onSuccess = {
                _direction.value = body()!!.points
                _polyLine.value = body()!!.polyline.replace("\\", """\""")
                _transit.value = _direction.value!![index].instruction
                _type.value = _direction.value!![index + 1].let { " With ${it.mode}" } 
            }
        }

    }

    fun compareLocation(lat: Double, lng: Double) {
        direction.value?.let { directionLiveData ->
            if (index < directionLiveData.size - 1) {                
                val direction = directionLiveData[index + 1]
                direction.lat = String.format("%.5f", direction.lat).toDouble()
                direction.lng = String.format("%.5f", direction.lng).toDouble()

                if (direction.lat - 0.001 < lat.toFive() && lat.toFive() < direction.lat + 0.001) {
                    if (direction.lng - 0.001 < lng.toFive() && lng.toFive() < direction.lng + 0.001) {                        
                        index++
                        _transit.value = directionLiveData[index].instruction
                        _type.value = directionLiveData[index + 1].let { " With ${it.mode}" }
                    }
                }
            } else if (directionLiveData.size != 0) {
                travelFinishEvent.call()
            }
        }
    }

    fun Double.toFive() = String.format("%.5f", this).toDouble()
}

์œ„๋Š” ์‚ฌ์ „ ์ƒํ™ฉ ์„ค๋ช…์— ์˜ ์กฐ๊ฑด๋“ค์— ๋”ฐ๋ผ์„œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

??? ์™œ MutableLiveData๋Š” _๊ฐ€ ๋ถ™์–ด์žˆ๊ณ  LiveData๋Š” ์—†์–ด์š”?

๋ทฐ์—์„œ ๋ทฐ๋ชจ๋ธ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฐธ์กฐํ•  ๋•Œ์—๋Š” MutableLiveData๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ , View์— ๋…ธ์ถœํ•  ๋•Œ์—๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋Š” LiveData๋กœ ์น˜ํ™˜์‹œ์ผœ View์—์„œ ViewModel์˜ ๋ฐ์ดํ„ฐ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์ฝ”ํ‹€๋ฆฐ ๊ณต์‹ ๋ฌธ์„œ์— ๋”ฐ๋ผ์„œ ์œ„์ฒ˜๋Ÿผ ์ฝ”๋“œ๋ฅผ ์งฐ๋Š”๋ฐ, Mutableํ•œ ๋ฐ์ดํ„ฐ๋Š” _๋ฅผ ๋ถ™์ด๊ณ , Immutable ํ•œ ๋ฐ์ดํ„ฐ๋Š” _๋ฅผ ๋ถ™์ด์ง€ ์•Š๊ฒŒ ๋งŒ๋“ค์–ด ๋ฐ์ดํ„ฐ๋ฅผ ๋…ธ์ถœ์‹œํ‚ต๋‹ˆ๋‹ค.

  • ์ฒ˜์Œ ์‹œ์ž‘ํ•  ๋•Œ, getNavigation() ์„ ํ†ตํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋ฉฐ, ์„ฑ๊ณต ์‹œ LiveData์— ๋ฐ์ดํ„ฐ๋ฅผ ์ง‘์–ด๋„ฃ์Šต๋‹ˆ๋‹ค.

  • compareLocation() ํ•จ์ˆ˜๋Š” View๋กœ๋ถ€ํ„ฐ ํ˜ธ์ถœ ๋  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค. ์œ„๋„์™€ ๊ฒฝ๋„๋ฅผ ์ธ์ž๋กœ ๋ฐ›์œผ๋ฉฐ, ๋ฐ›์€ ์œ„์น˜๊ฐ€ ๋ชฉ์ ์ง€์™€ ์ธ์ ‘ํ•˜๋ฉด ๋‹ค์Œ ๋ชฉ์ ์ง€๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

  • transit๊ณผ type์„ DataBinding์„ ์ด์šฉํ•˜์—ฌ xml์— ๋ฐ”์ธ๋”ฉ ํ•ด ๊ฐ€์•ผ ํ•  ๋ชฉ์ ์ง€ ์™€, ๊ฐ€์•ผ ํ•  ๋ฐฉ๋ฒ•์„ ๋„์›Œ์ค๋‹ˆ๋‹ค.

xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewModel"
            type="com.justgo.ui.navigation.NavigationViewModel" />
    </data>

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorPrimary"
        tools:context="com.justgo.ui.navigation.NavigationActivity">

        <android.support.constraint.ConstraintLayout
            android:id="@+id/navigation_trans_container"/>

            <ImageView                              
                android:src="@drawable/ic_bus_24px" />

            <TextView
                android:id="@+id/navigation_trans_tv"                
                android:text="To"
                android:textColor="#AFFF" />

            <TextView
                android:id="@+id/navigation_trans_location_tv"
                android:textColor="#FFF"
                android:text="@{viewModel.transit}" />

        </android.support.constraint.ConstraintLayout>

        <android.support.constraint.ConstraintLayout
            android:id="@+id/navigation_tag_container">

            <ImageView
                android:src="@drawable/ic_bookmark_24px" />

            <TextView
                android:id="@+id/navigation_tag_location_tv"
                android:textColor="#FFF"
                android:text="@{viewModel.type}"/>

        </android.support.constraint.ConstraintLayout>       

    </android.support.constraint.ConstraintLayout>
</layout>

์œ„ ์ฝ”๋“œ๋Š” ๋„ค๋น„๊ฒŒ์ด์…˜์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค. ์•„๋ž˜ ๋ ˆ์ด์•„์›ƒ์—์„œ ํŒŒ๋ž€์ƒ‰ ์„ ์œผ๋กœ ๊ฐ์‹ธ์ง„ ๋ถ€๋ถ„์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

Navigation

android:text="@{viewModel.transit}", android:text="@{viewModel.type}" ๊ณผ ๊ฐ™์ด ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ์ด์šฉํ•˜์—ฌ ์•กํ‹ฐ๋น„ํ‹ฐ์˜ ๋ณ„ ๊ฐ„์„ญ ์—†์ด ๋ฐ”๋กœ xml์— ๋ฐ”์ธ๋”ฉ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Activity

class NavigationActivity : DataBindingActivity<ActivityNavigationBinding>(), OnMapReadyCallback {
    override fun getLayoutId(): Int = R.layout.activity_navigation

    val viewModel by lazy { ViewModelProviders.of(this)[NavigationViewModel::class.java] }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding.viewModel = viewModel
        
        viewModel.getNavigation(transType.toString(), lat, lng, desLat, desLng)
        
        viewModel.travelFinishEvent.observe(this, Observer {
            toast("Travel Is Finish!")
            val arriveIntent = Intent(this, ArriveActivity::class.java)
            arriveIntent.putExtra("placeid", placeId)
            startActivity(arriveIntent)
            finish()
        })
    }

    override fun onMapReady(map: GoogleMap) {
        
        viewModel.polyLine.observe(this, Observer {
            val decoded = PolyUtil.decode(it)
            map.addPolyline(PolylineOptions().addAll(decoded)
                    .width(5F))
        })

        viewModel.direction.observe(this, Observer { direction ->
            direction!!.forEach { point ->
                LatLng(point.lat, point.lng).let {
                    map.addMarker(MarkerOptions().position(it))
                }
            }
        })

        
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 3000, 1F, object : LocationListener {
            override fun onLocationChanged(location: Location) {
                CameraUpdateFactory.newLatLngZoom(LatLng(location.latitude, location.longitude), 17F).let {
                    map.animateCamera(it)
                    viewModel.compareLocation(location.latitude, location.longitude)
                }
            }
        }
    }
}

MVVM ํŒจํ„ด์—์„œ๋Š” ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ์ด์šฉํ•˜์—ฌ xml์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”๋กœ ๋ฐ”์ธ๋”ฉ ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ์ง€๋„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋Š” ์–ด์ฉ” ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ๋Š” Activity๋‚˜ Fragment์—์„œ ์ฒ˜๋ฆฌํ•˜์ง€๋งŒ, ๊ทธ๋Ÿฐ ํŠน๋ณ„ํ•œ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด xml์—์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

  • Activity, ์ฆ‰ View์—์„œ๋Š” ViewModelProviders๋ฅผ ์ด์šฉํ•˜์—ฌ ์ƒ๋ช…์ฃผ๊ธฐ์— ์•ˆ์ „ํ•œ ViewModel ์„ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค.
  • DataBindingActivity<ActivityNavigationBinding>()์€ ๋ฐ์ดํ„ฐ๋ฐ”์ธ๋”ฉ์„ ํŽธํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“ AppCompatActivity์„ ์ƒ์†๋ฐ›์€ ์ถ”์ƒ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. getLayoutId() ์—์„œ ๋ ˆ์ด์•„์›ƒ ์•„์ด๋””๋ฅผ ๋ฐ›๊ณ , ์ œ๋„ค๋ฆญ์—์„œ ๋ฐ›์€ ํด๋ž˜์Šค๋ฅผ ์ด์šฉํ•˜์—ฌ binding์ด๋ผ๋Š” ์ด๋ฆ„์„ ๊ฐ€์ง„ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. binding ๊ฐ์ฒด์— viewModel์„ ๋ฐ”์ธ๋”ฉํ•˜์—ฌ xml์—์„œ ViewModel์˜ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ๋” ํ•ฉ๋‹ˆ๋‹ค.
  • View์—์„œ๋Š” travelFinishEvent, direction, polyline์„ ๊ด€์ธกํ•˜๋ฉฐ ๋ฐ์ดํ„ฐ์˜ ๋ณ€ํ™”๊ฐ€ ๊ฐ์ง€ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

travelFinishEvent

  • ์—ฌํ–‰์ด ๋๋‚ฌ์„ ๋•Œ ํ˜ธ์ถœ๋˜๋ฉฐ, ArriveActivity๋ผ๋Š” ์ƒˆ๋กœ์šด ์•กํ‹ฐ๋น„ํ‹ฐ๋ฅผ ๋„์›Œ์ค๋‹ˆ๋‹ค.
  • ์•กํ‹ฐ๋น„ํ‹ฐ๋ฅผ ์ „ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Navigator ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ •์˜ํ•˜์—ฌ ViewModel์—์„œ ์ฒ˜๋ฆฌํ•˜๊ฑฐ๋‚˜ SingleLiveEvent๋ฅผ ์ด์šฉํ•˜์—ฌ ์•กํ‹ฐ๋น„ํ‹ฐ์—์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋Š”๋ฐ, ํ›„์ž์˜ ๋ฐฉ๋ฒ•์ด ๊ฐ„๊ฒฐํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ›„์ž์˜ ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.

polyLine / direction

  • ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋ฉด polyLine๊ณผ direction์— ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด์ฃผ๋Š”๋ฐ, VIew์—์„œ ์ด LiveData๋ฅผ Observe ํ•˜๋ฉฐ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด์™”์„ ๋•Œ ๋งต์— ํด๋ฆฌ๋ผ์ธ๊ณผ ๋งˆ์ปค๋ฅผ ๊ทธ๋ ค์ค๋‹ˆ๋‹ค.
  • travelFinishEvent์™€ ๋™์ผํ•˜๊ฒŒ SingleLiveEvent๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•˜์—ฌ๋„ ๋ฌด๊ด€ํ•˜์ง€๋งŒ, LiveData์˜ Observe๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ๋กœ์ง์„ ๊ตฌ์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค.

viewModel.compoareLocation

  • 3์ดˆ๋งˆ๋‹ค ์œ„์น˜ ์ •๋ณด๋ฅผ ๊ฐฑ์‹ ํ•˜๋ฉฐ viewModel.compareLocation ์„ ํ˜ธ์ถœํ•˜๋ฉฐ ํ˜„์žฌ ์œ„์น˜์™€ ๋ชฉ์ ์ง€๋ฅผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.
  • ์œ„์น˜ ์ •๋ณด ๊ฐฑ์‹ ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ์—” ๋ฌด๋ฆฌ๊ฐ€ ์žˆ์–ด์„œ ์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” View(Activity)์—์„œ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์œ„ ๋ฐฉ๋ฒ•์ด ์˜ณ์€ ๋ฐฉ๋ฒ•์œผ๋กœ MVVM ํŒจํ„ด์„ ๊ตฌํ˜„ํ•œ ๊ฒƒ์ด๋ผ๊ณ  ํ™•์‹ ํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์œ„ ์ฝ”๋“œ๋ฅผ ๋ฆฌ๋ทฐํ•˜๋ฉฐ MVVM ํŒจํ„ด์— ๋Œ€ํ•œ ๊ฐˆํ”ผ๋ฅผ ์žก์œผ์…จ์œผ๋ฉด ํ•˜๋Š” ๋ฐ”๋žจ์ž…๋‹ˆ๋‹ค! ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.