AW211: findViewById 벗어나기, MVP모델에 대한 고찰

Android Weekly는 매주 발행되는 안드로이드 뉴스레터입니다. 영어 기사를 정독할 시간이 없는 분을 위해 핵심 꼭지를 요약했습니다.

주간 안드로이드 뉴스를 요약해 드립니다. Android Weekly 211 원문도 읽어보세요.


findViewById 이제 그만!

TextView hello = (TextView) findViewById(R.id.hello);

매번 findViewByID를 작성하는것 힘드셨죠? findViewByID는 이제 그만! (No More findViewById)에서 데이터 바인딩을 활용해 번거로운 작업을 간단히 처리하는 방법을 알려줍니다. 외부 라이브러리를 사용하는 방법이 아닌 Android Studio 1.5이상 버전에서 지원되는 공식적인 방법입니다.

  • STEP1 : build.gradle 파일 설정
android {
    
    dataBinding.enabled = true
}
  • STEP2 : 태그로 레이아웃 감싸기 Android Studio는 태그로 감싼 레이아웃에 대해 컴파일타임에 추가적인 처리를 진행합니다. 예를 들어 관심있는 view를 찾아 다음 처리를 할 수 있도록 알려줍니다.
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
    <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:paddingLeft="@dimen/activity_horizontal_margin"
            android:paddingRight="@dimen/activity_horizontal_margin"
            android:paddingTop="@dimen/activity_vertical_margin"
            android:paddingBottom="@dimen/activity_vertical_margin"
            tools:context=".MainActivity">

        <TextView
                android:id="@+id/hello"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

    </RelativeLayout>
</layout>

이런 개발 뉴스를 더 만나보세요

  • STEP3 : 런타임에 레이아웃파일 로드하기 새로 처리된 레이아웃을 부르기 위해 로딩 절차가 약간 달라집니다. (기존 방식이 위, 새로 적용해야 할 방식이 아래)
setContentView(R.layout.hello_world);
TextView hello = (TextView) findViewById(R.id.hello);
hello.setText("Hello World"); // 기존 방식
HelloWorldBinding binding =
    DataBindingUtil.setContentView(this, R.layout.hello_world);
binding.hello.setText("Hello World");


데이터 바인딩을 적용한 코드에서 보면 hello_world.xml에 대해 HelloWorldBinding 클래스를 생성하고 여기서 id가 hello인 뷰를 final 필드로 바로 사용할 수 있습니다. 클래스와 변수 이름은 카멜표기법을 기준으로 변환되니 참고하세요.

” 캐스팅도, findViewById도 없이 더 빠르고 쉽게 처리 가능합니다”

바인딩 절차는 findViewById를 사용하는 것이 뷰 위계를 한 단계씩 거쳐서 특정 뷰를 찾는 것과 비교할 때 레이아웃에 있는 모든 뷰에 대해 하나의 패스를 구성하여 더 빨리 처리 됩니다.

  • LayoutInflater를 사용할 때 리싸이클러뷰, 뷰페이저 등 비명시적 변환을 허용하지 않는(type-safe) 메소드를 사용하고 싶으시다면 여러가지 버전이 가능합니다. 다음 코드 중 더 적합한 방식으로 사용하시면 됩니다.
HelloWorldBinding binding = HelloWorldBinding.inflate(
    getLayoutInflater(), container, attachToContainer);

만약 inflated된 뷰를 컨테이너 뷰그룹에 붙일게 아니라면 아래와 같이 사용할 수 있습니다.

linearLayout.addView(binding.getRoot());

정리하면, 데이터 바인딩을 활용한 findViewById를 대체하는 방법은 런타임시에 reflection이나 고비용 기술을 요구하지 않는 방법이므로 현재 가지고 계신 어플리케이션에 바로 적용하셔도 무리 없는 방법이 될테니 참고하셔서 좀 더 편하고 빠른 개발환경을 갖추시길 바랍니다 :)

MVP를 둘러싼 고찰

  • Presenter를 위한 interface가 정말 필요할까요? 아래 그림과 같은 스키마에서 모델은 여러분의 비지니스 로직을 구형하는데 필요한 모든 코드와 관련되어 있습니다. 프레젠터는 프레젠테이션의 로직을 구현한 클래스이고 뷰는 abstract implementation 뷰를 만들기 위한 인터페이스죠.
    그렇다면 왜 이 패턴에서 뷰는 인터페이스로 구현되어야 할까요? 구현 뷰와 코드를 분리하고 싶어서겠죠. 그래야 외부 의존과 상관없이 프레임워크를 추상화 할 수 있을테니까요. 또 다른 이유는 유닛 테스트하기에 적합하기 때문이라고 많이들 생각하실 겁니다.
    그런데 MVP에서 Presenter를 위한 interface는 시간 낭비다! (Interfaces for presenters in MVP are a waste of time!)에서 기계적으로 MVP모델을 사용하는 것에 대해 한 번쯤 짚고 넘어갈만한 부분을 다루고 있습니다.
    위 글의 주요 내용은 1. implmentation이 둘 이상일 경우, 2. 코드와 써드파티와의 경계를 구분지어야 할 때를 제외하고는 인터페이스를 사용하는 것은 불필요한 일이라고 주장합니다. 무엇보다 인터페이스 없이도 abstraction 생성을 위한 코드 분리가 가능하기 때문입니다. 자세한 내용은 원문을 참고해주세요.

  • 뷰 대신 수동적인(passive) 뷰 MVP모델을 구현하면서 “이걸 프레젠터에 둘까? 아니면 액티비티에 둘까?” 또는 “REST API로부터 받아온 데이터를 어디서 조작해야하나?”에 대해 많이들 고민하셨을겁니다.
    프레젠테이션 모델 그리고 수동적인 뷰 (Presentation Model and Passive View in MVP — The Android way)의 저자는 위와 같은 고민이 있을 때 모두 프레젠터로 옮겼다고 하는데요, 이렇게 해결할 경우 유닛 테스트를 하는게 편해지지만 유지 보수나 가독성이 떨어지는 문제가 생깁니다. 이런 문제를 해결하는 과정에서 새로운 모델로써 [ 프레젠테이션 모델 그리고 수동적인 뷰 ]를 제안합니다.(아래 오픈소스 라이브러리에서 소개되는 DroidMVP를 참고하세요.)

  • View: 액티비티, 프래그먼트, 커스톰 뷰 등 필요에 따라 달라질테지만 뷰의 주요 책임은 사용자에게 어떤 상태를 제시한다는 점입니다. 프로그레스바 또는 텍스트, 다이얼로그 등 사용자에게 의미 있는 것을 제시하는 부분이죠. 기억해야 할 점은 어떤 것을 “언제”가 아니라 “어떻게” 제시 할 것인가를 알아야 합니다. 이때 뷰를 passive 뷰 즉, 가능한 멍청;하게 또는 수동적으로 만들면 모든 로직을 안드로이드UI와 관련되지 않은 클래스로 옮길 수 있기 때문에 UItest를 최소화 할 수 있습니다.(UI테스트를 unit테스트로 처리할 수 있는 것이죠~)

    • Presenter: 뷰의 컨트롤러입니다. 어떤 상태를 “언제” 제시할지 알아야 하는 부분입니다. 모든 비지니스 로직과 프레젠테이션 로직을 프레젠테이션 모델로 옮기면 프레젠터 코드는 더 깔끔하고 가독성을 높일 수 있습니다.

    • Presentation Model: 무엇이 제시되어야 하는지를 알아야 하는 부분으로 앱의 핵심을 다루는 부분입니다. 주요 특징을 정리하면 아래와 같습니다.

      • 데이터는 모델에 저장하되 데이터를 바꿀 수 있는 것은 오직 모델만 가능하도록 만든다.
      • 액티비티와 프래그먼트과 함께 저장되고 복구 될 것이기 때문에 프레젠테이션 모델을 Serializable하게 만든다.
      • 유닛테스트하기에 간편하며 무엇보다 중요한 것은 나중에 디버그 하는 것보다 먼저 테스트하는게 더 ‘쉬운’작업이기 때문에 모델의 테스트 커버리지는 거의 100에 가깝게 만든다.

이처럼 MVP모델을 사용함에 있어 여러 개발자가 자신들의 경험에 근거한 사용 팁을 제시하고 있습니다. 개발 중 직접 마주한 불편점들을 해결하면서 다양한 변형또는 개선된 방법을 제시하는데요, 여러분도 혹시 같은 고민이 있으셨다면 이번주 관련 원문들을 참고하시면 좋겠습니다.

오픈소스 라이브러리

  • Quill Quill은 퍼블리싱 플랫폼인 Ghost블로그의 안드로이드 앱입니다.

  • EspressoDescendantActions 기본 사용은 아래 스니펫과 같습니다. 리싸이클러뷰의 자식뷰와 같은 자손 뷰에 대한 커스텀 에스프레소 액션을 수행할 수 있도록 특정 자손뷰에 접긑하는 것이 가능해졌습니다.

onView(withId(R.id.recyclerView)).perform(
    actionOnItemAtPosition(5,
        DescendantViewActions.checkViewAction(matches(isCompletelyDisplayed())))
);
  • DroidMVP MVP를 구현할 수 있도록 도와주는 작은 라이브러리입니다.

더 읽을 거리

6월 다섯째 주의 기사를 Android Weekly 211 영어 원문에서 볼 수 있습니다.

지난 뉴스가 궁금하다면 아래 링크를 참고해 주세요.

컨텐츠에 대하여

이 컨텐츠는 저자의 허가 하에 이곳에서 공유합니다.


Realm Korea

Realm Korea Team

4 design patterns for a RESTless mobile integration »

close