Android | MVP Presenter/View/Model Code Lab

By Moka
image

MVP 의 대한 설명은 Android - MVP in action Intro 에서 참고하시기 바랍니다.

여기서는 코드를통해 MVP 를 어떻게 구현해나가는지 살펴보도록 하겠습니다. 예시 코드는 Kotlin 으로 적도록 하겠습니다. 보는것은 Java 보듯이 보면 됩니다.


예제코드 - GitHub

https://github.com/moka-a/MvpSample

아래 예제의 JAVA 코드들을 github 에 만들어 두었습니다. 각 step 에 따라 브런치를 달리 하여 놓았고, 최종 코드는 master 브렌치를 보시면 됩니다.


동영상

https://youtu.be/Pydw-dzy2Vg

링크로 가시면, MVP 구조를 잡아가는 라이브 코딩을 보실수 있습니다.


View

가장 먼저 뷰를 살펴보도록 하겠습니다. 우선 Fragment 자체가 뷰가 될것이기 때문에 일단은 아무것도 하지 않습니다.

// MyFragment.kt
class MyFragment : Fragment() {

  override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return super.onCreateView(inflater, container, savedInstanceState)
    }

}


Presenter

Fragment ( MVP 의 View ) 하나당 Presenter 를 하나를 가지도록 합니다. 우선 Presenter 클래스를 하나 만들도록 합니다. GitHub 예제 코드는 https://github.com/moka-a/MvpSample/tree/step_02 에 있습니다.

// MyPresenter.kt
class MyPresenter {
}

View 와 Presenter 를 만들었습니다. 이전 포스팅에서 View 에서 시작되서 Presenter 로 흐름이 이어진다고 했습니다. 그러려면 View 가 Presenter 를 알고 있어야 될것입니다. 따라서 위에서 만들어준 Presenter 의 객체를 생성 하도록 합니다.

그리고 또한 Presenter 도 연산이 끝나고 화면을 갱신해주기 위해선는 View 를 알고있어야 합니다. (View 의 인스턴스를 가지고 있어야 합니다) attachView() 라는 함수를 만들어 Presenter 에도 넘겨줍니다.

// MyFragment.kt
class MyFragment : Fragment() {
  // ...
  private var presenter : MyPresenter? = null

  override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        presenter = MyPresenter()
        presenter.attachView(this)
  }

}

// MyPresenter.kt
class MyPresenter {
  private var view: Fragment? = null

  fun attachView(view: Fragment) {
      this.view = view
  }
}

자 이제 사실상 끝이긴 합니다. Fragment 에서 Presenter 에 attachView() 를 한후에 원하는 로직을 Presenter 에서 함수로 만들어 호출 하고 화면 갱신을 원하면 가지고 있는 fragment( view ) 인스턴스를 통해 함수호출 하면 됩니다. 그리고 Presenter 에서 데이터가 필요할때 Model 에서 가져오는 것입니다.

하지만

위와 같이 하면 MVP 의 의도를 전혀 살리지 못합니다 이전 포스팅에서 말했던 View 와 Presenter 간의 의존성이 없어 진다는 것이 성립 하지 못합니다. 특히 Presenter 에서 View 에 대한 의존도를 없애야 하는데 ( 테스트의 용이성을 위해 ) 위와 같이 fragment 의 인스턴스를 가지고 있다면 그렇지 못합니다.


View 를 Interface 를 통해서

MvpView 라는 interface 를 만들어서 View 의 역활을 하고있는 Fragment 가 MvpView 인터페이스를 구현합니다.
GitHub 예제 코드는 https://github.com/moka-a/MvpSample/tree/step_03 에 있습니다.

// MvpView.kt
interface MvpView {

}

// MyFragment.kt
class MyFragment : Fragment(), MvpView {
  // ...
  private var presenter : MyPresenter? = null

  override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        presenter = MyPresenter()
        presenter.attachView(this)
  }
}

// MyPresenter.kt
class MyPresenter {
  private var view: MvpView? = null

  fun attachView(view: MvpView) {
      this.view = view
  }
}

위의 Presenter 를 보면 MvpView 인터페이스의 인스턴스를 가짐으로써 Fragment 로 부터 의존도를 낮추게 되었습니다. 실제 코드에서는 Fragment 가 해당 인터페이스로 동작 함으로써 동작 하게 될것 이고, 테스트를 할때는 View 를 간단하게 Mocking 하여 테스트를 할수가 있을 것입니다.

다른 샘플들을 보면 View 에서 Presenter 또한 인터페이스를 통해 구현하는것이 있고, 안그런것들이 있는데, 여기서는 그렇게 하지 않았고, 제 샘플 프로젝트에도 굳이 하지 않았습니다. 왜냐하면 View( Fragment ) 는 따로 Unit 테스트를 하지 않을것이기 때문입니다.


Model 또한

Presenter 는 필요한 데이터를 모델을 통해서 가져 오게 될것 입니다. 이때 Model 또한 interface 를 하나 만들어서 구현하고, Presenter 에서는 interface 의 인스턴스를 가지고 있으며 필요한 데이터를 가지고 오도록 합니다. GitHub의 예제 코드 에서는 Repository 라고 만들어 데이터를 가져오고 있습니다.

https://github.com/moka-a/MvpSample/tree/step_04 의 브렌치 step_04 를 보시면 Repository 또한 interface 를 통해 값을 가져오는것을 볼수가 있습니다.


마무리

사실 mvp 든 mvc 든 코드를 적당히 분리 해놓는 거라고 생각합니다. 하지만 비지니스 로직을 테스트 하기 용이하도록 Presenter 라는 클래스를 만들어 분리한것이라고도 볼수 있습니다.


다른 MVP 포스팅 보기 및 샘플 프로젝트