공상하는 개발자

[아키텍쳐 패턴] 3탄 : MVVM 패턴 적용하기. 본문

개발/안드로이드

[아키텍쳐 패턴] 3탄 : MVVM 패턴 적용하기.

공상과학소설 2020. 4. 19. 20:29
반응형

2탄 MVP 패턴을 포스팅 한 이후 오랜 시간이 흘렀다..

그동안 MVVM에 대한 공부를 하고,

프로젝트에 적용하면서 조금은 익숙해진 것 같아 포스팅을 하려고 한다.

언제나 날카로운 피드백은 환영이다.

 

드루와

...


MVVM (Model + View + ViewModel)

정의

-> 모델 + 뷰 + 뷰모델의 구조이다. MVP 패턴에서 프레젠터가 뷰모델로 바뀐 것으로, 뷰와 프레젠터의 의존성을 없앨 수 있는 패턴이다.

 

· Model : 프로그램에서 사용되는 실제 데이터 및 데이터 로직을 처리하는 부분.

                ex) retrofit을 필요한 데이터를 받아오는 것.

· View : 사용자의 입력을 받고, 보여주는 부분.

· ViewModel : View에게 정보를 뿌려주기 위한 View를 위한 Model

 


MVVM의 핵심!

ViewModel은 View를 알지 못하고, View는 Model을 알지 못하지만 ViewModel은 안다. 그렇기에 View는 ViewModel을 계속해서 observe 하다가 변화가 생기면 UI 로직에 의해 출력을 발생시킨다. (oberver pattern 이용)

Databinding

  • View와 ViewModel을 독립시켜줌.
  • ViewModel의 LiveData를 계속해서 관찰해서 변화가 생기면 로직을 통해 xml에서 편하게 바꿔줄 수 있다.
  • LifeCycleOwner 가 필요하다.

VisitedPlaceActivity.kt

class VisitedPlaceActivity : AppCompatActivity() {

    lateinit var placeAdapter: PlaceAdapter

    val vm: VisitedPlaceViewModel = VisitedPlaceViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityVisitedPlaceBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_visited_place)

        binding.vm = vm
        binding.lifecycleOwner = this

        placeAdapter = PlaceAdapter()
        binding.actSearchRv.adapter = placeAdapter

        binding.btnSearch.setOnClickListener {
            vm.getAddress(binding.actSearchEtSearch.text.toString())
        }

    }
}

1. search 버튼을 누르게 되면 VisitedPlaceViewModel의 getAddress 함수를 부르게 된다. ( View에서 ViewModel을 부름.)

VisitedPlaceViewModel.kt

class VisitedPlaceViewModel : ViewModel() {
    private val addressRepository: AddressRepository = AddressRepository()
    private val disposables = CompositeDisposable()
    val PlaceList = MutableLiveData<List<Place>>()

    fun getAddress(search: String) {
        disposables.add(addressRepository.getAddress(search) //RxJava 이용
            .observeOn(AndroidSchedulers.mainThread())
            .doOnSubscribe {
            }
            .doOnTerminate {}
            .subscribe {	// 구현 로직
                PlaceList.value = it.message	//Place를 담은 리스트를 PlaceList에 넣어준다.
            }
        )
    }
}

2. getAddress가 View로부터 불려지면 RxJava를 이용해 로직을 실행시켜준다. 

(RxJava의 정보가 궁금하다면 밑의 링크에 들어가 보길 추천한다.)

 

안드로이드 프로젝트에 RxJava 적용하기

RxJava를 적용하여 비동기 작업이나 UI 이벤트를 효율적으로 처리해보자! 1. RxJava란? Reactive Extensions RxJava를 알아보려면, 먼저 'Reactive Extensions'에 대해 알아야 한다. 얘는 ReactiveX라고도 부르며,..

ssionii.tistory.com

activity_visited_place.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="vm"
            type="com.example.earlybuddy_pattern.ui.visited.VisitedPlaceViewModel" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.search.SearchActivity">

        /*
        ...
        */
          
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/act_search_rv"
            setPlaceData="@{vm.placeList}"  // ViewModel의 placeList가 들어오는 부분.
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/act_search_cl_search" />


    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

 

3. 2번에서 PlaceList의 값이 갱신되면 xml에 DataBinding으로 연결된 부분이 갱신된다. 위의 경우에는 setPlaceData라는 BindingAdapter로 RecyclerView에 데이터를 넣어주고 있다. 

BindingAdapter.kt

@BindingAdapter("setPlaceData")
fun RecyclerView.setData(placeData: List<Any>?) {
    when (placeData) {
        null -> {
        }
        else -> {
            if (placeData.isNotEmpty()) {
                (this.adapter as PlaceAdapter).setListData(placeData as List<Place>)
                // setListData는 리사이클러뷰에 데이터를 넣어주는 메소드.
            }

        }
    }
}

4. setPlaceData의 형태는 위와 같다.


결과

결과는 잘 나온다.


결론

위와 같이 로직을 분리하여 유지보수를 편하게 하는 것이 아키텍처 패턴을 쓰는 이유이다. 웬만한 아키텍처 패턴은 얼추 익힌 거 같으니 프로젝트에 적용을 시켜볼까...(얼리 버디 파이팅...!)

반응형
Comments