ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • RecyclerView in MVP - Passive view's approach -1
    디자인패턴 2017. 7. 26. 00:09
    반응형

    RecyclerView in MVP - Passive view's approach

    좋은 프로그래머는 Seperation of concerns를 잘 알아야한다. 그래야 코드가 왜 이렇게 작성됐는지 모르는 사람도 읽기 쉬운 코드를 작성할 수 있다. 

    '나는 코드 뭉치를 presenter에 넣어야할까? activity에 넣어야할까?', 'REST API로부터 받아야하는 데이터는 언제 실행해야할까?' 항상 고민한다. God Activity object를 만들지 않기 위해서 나는 이러한 모든 고민을 presenter에 넣는다. 하지만 이렇게 해도 presenter가 할 일이 많아지면 읽기 힘들기는 마찬가지이다. 

    나는 여기서 DroidMVP라는 라이브러리를 소개하고자 한다. 이 라이브러리에서 추구하고자 하는 바를 자세히 알아보고자 한다. 

     


    View = HOW

    Activity, Fragment, a custom view를 말한다. 

    1) 가장 큰 책임은 progress bar, text, dialog 등 유저에게 의미있는 상태 등 모든 것을 보여줄 책임을 가진다.  

    2) Android widget들은 유저의 눈에 보이는 것을 표현하도록 도와주므로 Android widget들을 특정 Action으로  다뤄야하는데, 언제가 아니라 어떻게 표시할지가 중요하다. 

    showProductsList(List<Product> products);
    showProgress();
    showUpdateCompleted();


    Android Widget을 언제 표시하고 안 할지는 View의 책임이 아니므로 아래와 같이 작성하면 좋지 않다.

    public class BadActivity extends DroidMVPActivity<GoodPresentationModel, GoodView, GoodPresenter> { ... @OnClick(R.id.button) public void onButtonClicked() { showProgress(); presenter.onButtonClicked(); } ... }


    아래와 같이 Progressbar를 표시할지 표시하지 않을지는 Presenter가 결정하도록 해야한다.

    public class GoodActivity extends DroidMVPActivity<GoodPresentationModel, GoodView, GoodPresenter> {
        ...
        
        @OnClick(R.id.button) public void onButtonClicked() {
            presenter.onButtonClicked();
        }
        
        ...
    }
    
    --
    public class GoodPresenter extends SimpleDroidMVPPresenter {
        ...
        
        public void onButtonClicked() {
          getMvpView().showProgress();
          // request data from API
        }
        
        ...
    }


    View 는 최대한 멍청하게, 수동적으로 만들어야한다. 즉, View 관련한 로직(Progress bar를 띄우는 등)은 가능한한 Android-related 하지 않은 클래스를 작성해야한다. 그러므로 UI Test는 분리해 작성할 수 있다. 


    Presenter - WHEN

    Presenter는 말 그대로 View의 컨트롤러이다. 

    1) View의 상태(Model 포함) 변경과 user의 여러 동작, Domain Layer(like Rest API, or Usecase classes)를 대신(delegate, trigger) 요청하는 역할이다. 

    2) Presenter는 View의 상태를 언제 표시할지 알고 있고, Presentation Model을 변경하는 trigger 역할이다. 

    예를 들면, user가 버튼을 클릭하면, presenter가 View에게 Progress bar를 표시하라고 알려주고, Presentation Model안에 데이터를 update하고 그 결과를 Presentation Model에 저장하여 Presentation Model에 변경된 Data로 View의 상태를 update하라고 요청한다. 

    public class GoodPresenter extends SimpleDroidMVPPresenter<GoodView,GoodPresentationModel> { public void onShowUsersButtonClicked() { getMvpView().showProgress(); repository.getUsers(new Callback() { public void onSuccess(List<Users> users) { usersListFetched(users); } public void onFailure(Exception e) { usersListFetchError(e); } }) } private void usersListFetched(List<Users> users) { PresentationModel model = getPresentationModel(); model.setUsers(users); if (getMvpView() == null) { return; } if (model.shouldDisplayEmptyState()) { getMvpView().showEmptyState(); } else { getMvpView().showUsersList(model.getAllUsers()); } if(model.hasAnyUserBirthdayToday()) { getMvpView().showBirthdayAlert(model.getUsersWithBirthday()); } } private void usersListFetchError(Exception e) { getPresentationModel().setUsersListFetchError(e); if (getMvpView() != null) { getMvpView().showUsersListFetchError(); } } }


    Business Logic[1]을 Domain Layer에 대신 요청하고, View의 logic을 Presentation Model에 대신 요청함으로써 Presenter는 View의 상태를 controlling하는 책임을 가진다. 

    모든 Business logic과 Presentation logic을 Presenter로부터 멀리함으로써 우리의 Presenter는 더욱 읽기 쉽고 "WHEN something should happen"에 집중할 수 있다. HOW, WHAT은 신경쓰지 않아도 된다.  


    Presentation Model - WHAT

    가장 중요한 부분이다. Presentation Model에 Domain model을 전달할 뿐만 아니라 View의 현재 State(여기서 State는 Model을 말하는듯)를 가지고 있다. Presentation Model은 무엇이 user에게 표시될지 알고 있다. 이 무엇은 Presenter에 의해 대신 불려진다. 

    예를 들면, user 목록이 API에 의해서 수정되었다면, Presenter는 그 user list를 Presentation Model안에 set하고, 그 수정된 Model이 알고 있는 data를 기반으로 user의 생일을 표시할지 안할지, 어떤 user의 View의 상태를 변경할지 결정한다.

    public class GoodPresentationModel implements Serializable {
    
        public static final Birthday TODAY = new Birthday();
        private List<User> users = Collections.emptyList();
    
        public void setUsers(List<User> users) {
            this.users = users;
        }
    
        public boolean shouldDisplayEmptyState() {
            return users.isEmpty();
        }
    
        public List<User> getAllUsers() {
            return Collections.unmodifiableList(users);
        }
    
        public List<User> hasAnyUserBirthdayToday() {
            for (User user : users) {
                if (isBirthdayToday(user.getBirthday())) {
                    return true;
                }
            }
            return false;
        }
    
        public List<User> getUsersWithBirthday() {
            List<Users> result = new LinkedList<>();
            for (User user : users) {
                if (isBirthdayToday(user.getBirthday())) {
                    result.add(user);
                }
            }
            return Collections.unmodifiableList(result);
        }
    
        private boolean isBirthdayToday(Birthday birthday) {
            return birthday.getMonth() == TODAY.getMonth() && birthday.getDay() == TODAY.getDay();
        }
    }


    정리하자면 아래와 같다.

    . Model에 저장되는 Data는 Immutable해야한다. 외부에서 수정할 수 있도록 하면 돌연변이 데이터를 만들 수도 있다. 

    2. Activity와 Fragment에서 Model을 저장하고 복구할 수 있도록 Serializable 혹은 Pacelable하게 하자. 

    3. Android specific stuff는 제거하자. Unit test하기 훨씬 편리하다.

    View logic을 Android Framework와 Presenter로부터 분리함으로써 잠자고 있는 bugs를 줄이고 test를 좀 더 쉽게 작성할 수 있다. 


    ----

    참고 : https://android.jlelse.eu/presentation-model-and-passive-view-in-mvp-the-android-way-fdba56a35b1e

    • Business logic : determine how data can be created, stored, and changed.
    • Presentation logic : concerned with how business objects are displayed to users of the software 
      • e.g. the choice between a pop-up screen and a drop-down menu.
    • View logic : what need to show to user, should be treated as a meta-widget composed of Android ones (like TextView or ImageView) with the interface that exposes certain actions


    반응형

    '디자인패턴' 카테고리의 다른 글

    [Android] MVC vs MVP vs MVVM  (0) 2017.08.01
    RecyclerView in MVP — Passive view’s approach -2  (0) 2017.07.26
Designed by Tistory.