※ 이전 포스트 "안드로이드 mvc, mvp, mvvm" 을 보고 이 포스트를 보기를 추천한다. 안 된다면 "안드로이드 mvc, mvp, mvvm" 포스트에서 mvp 라도 봐라.


<구글 Basic MVP>의 구조이다. 왼쪽 REPOSITORY와 Remote data source, Local data source 는 Model 영역이다.



※ <MVP google sample> 예제

위 예제를 본따서 해당 포스트를 만들었다. mvp 구조만 파악할 것이기에 코드의 앞뒤가 안 맞는다는 점 이해 부탁드립니다.



위 프로젝트는 위에 링크 된 구글의 mvp 예제이다. 프로젝트 구조를 보면 각 Activity마다 하나의 패키지를 만들었다. 각 Activity 패키지 안에는 Presenter와 View를 추상화한 Contract 인터페이스, Presenter, View 역할을 하는 Activity 또는 Fragment가 있다. 또한, data 패키지 안 Model을 보면 Remote/Local을 구분하며, Memory cache를 하는 Repository 클래스, 서버를 통해 데이터를 불러오는 Remote 패키지, SQL, Realm 등을 통해 단말기 상의 데이터를 불러오는 Local 패키지로 구성되어 있다.



< Presenter와 View 구현 >

1. Contract 정의

public interface TasksContract {

    interface View {
        void setLoadingIndicator(boolean active);
        void showTasks(List<Task> tasks);
        void showSuccessfullySavedMessage();
        boolean isActive();
    }

    interface Presenter {
        void result(int requestCode, int resultCode);
        void loadTasks(boolean forceUpdate);
        void addNewTask();
        void setFiltering(TasksFilterType requestType);
        void openTaskDetails(Task requestedTask);
    }
}

Contract에는 Presenter와 View를 각각 정의한다. 하나의 interface에 View와 Presenter을 정의하고 이를 각각의 View와 Presenter 클래스에 정의하는 방식이다.


2. Presenter 구현

 

public class TasksPresenter implements TasksContract.Presenter { private final TasksRepository repository; private final TasksContract.View view; public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) { mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null"); mTasksView = checkNotNull(tasksView, "tasksView cannot be null!"); mTasksView.setPresenter(this); } @Override public void addNewTask() { mTasksView.showAddTask(); } @Override public void clearCompletedTasks() { mTasksRepository.clearCompletedTasks(); mTasksView.showCompletedTasksCleared(); loadTasks(false, false); } @Override public void loadTasks(boolean forceUpdate) { // Simplification for sample: a network reload will be forced on first load. loadTasks(forceUpdate || mFirstLoad, true); mFirstLoad = false; } ........ }

Contract.Presenter 인터페이스를 상속하여 구현했다. Presenter에서 View와 Repository에 접근해야 하므로 생성자를 통해서 View와 Repository 객체를 받았다. 물론 이전 포스트 "안드로이드 mvc, mvvm, mvp" 에 나오는 setView, setRepository 메서드를 사용해도 된다.


3. View 구현

- Activity

public class TasksActivity extends AppCompatActivity {
    private TasksPresenter mTasksPresenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.tasks_act);

     .........

         TasksFragment tasksFragment =
                (TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
        if (tasksFragment == null) {
            // Create the fragment
            tasksFragment = TasksFragment.newInstance();
            ActivityUtils.addFragmentToActivity(
                    getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
        }

         mTasksPresenter = new TasksPresenter(
                TasksRepository.getInstance() , tasksFragment);
    }
    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putSerializable(CURRENT_FILTERING_KEY, mTasksPresenter.getFiltering());

        super.onSaveInstanceState(outState);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                // Open the navigation drawer when the home icon is selected from the toolbar.
                mDrawerLayout.openDrawer(GravityCompat.START);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }
........

}

위와 같이 Fragment를 사용할 시 Activity는 단지 Presenter와 Model, View를 초기화해 연결해주는 역할과 Activity 생명으로써 역할만 한다. Fragment 가 실질적인 View 역할을 하므로 Contract.View는 상속하지 않았다. 


- Fragment

public class TasksFragment extends Fragment implements TasksContract.View {

    private TasksContract.Presenter mPresenter;
   
    public TasksFragment() {
        // Requires empty public constructor
    }

    public static TasksFragment newInstance() {
        return new TasksFragment();
    }

    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.tasks_frag, container, false);

        ......

        FloatingActionButton fab =
                (FloatingActionButton) getActivity().findViewById(R.id.fab_add_task);

        fab.setImageResource(R.drawable.ic_add);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPresenter.addNewTask();
            }
        });
 
        .....
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_clear:
                mPresenter.clearCompletedTasks();
                break;
            case R.id.menu_refresh:
                mPresenter.loadTasks(true);
                break;
        }
        return true;
    }


    @Override
    public void showAddTask() {
        Intent intent = new Intent(getContext(), AddEditTaskActivity.class);
        startActivityForResult(intent, AddEditTaskActivity.REQUEST_ADD_TASK);
    }

    @Override
    public void showCompletedTasksCleared() {
        showMessage(getString(R.string.completed_tasks_cleared));
    }

    private void showMessage(String message) {
        Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).show();
    }

}

View 역할을 하므로 Contract.View 인터페이스를 사속하여 구현했다. 



< Model 구현 >

1. DataSource 정의

public interface TasksDataSource {
    void clearCompletedTasks();
    void refreshTasks();
    void deleteAllTasks();
    void deleteTask(@NonNull String taskId);

    interface LoadImageCallback {
        void onImageLoaded(ArrayList<ImageItem> list);
    }
}

위 DataSource는 Repositor와 remote, local 안에 있는 메서드를 공통으로 정의한다.


2. Repository 정의

public class TasksRepository implements TasksDataSource {

    private static TasksRepository INSTANCE = null;
    private final TasksDataSource mTasksRemoteDataSource;
    private final TasksDataSource mTasksLocalDataSource;

     // Prevent direct instantiation.
    private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,
                            @NonNull TasksDataSource tasksLocalDataSource) {
        mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);
        mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);
    }

    public static TasksRepository getInstance(TasksDataSource tasksRemoteDataSource,
                                              TasksDataSource tasksLocalDataSource) {
        if (INSTANCE == null) {
            INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource);
        }
        return INSTANCE;
    }
}

위 코드를 보면 Repository가 DataSource를 상속받는 걸 알 수 있다. Repository에서 remote와 local을 접근하기 때문에 생성자에서 초기화한다. 나머지 local과 remote도 마찬가지로 DataSource를 상속받아 구현하면 된다.



< 참고 페이지 >


'안드로이드 > 기본' 카테고리의 다른 글

NDK(2) - 안드로이드 스튜디오 NDK JNI 기본 예제  (2) 2017.07.01
NDK(1) - JNI, Android.mk  (0) 2017.06.30
안드로이드 mvc, mvp, mvvm  (0) 2017.06.09
SharedPreferences  (0) 2017.06.04
action.Main, category.LAUNCHER  (0) 2017.06.04

+ Recent posts