Advertisement
  1. Code
  2. Coding Fundamentals
  3. Design Patterns

Cara Mengadopsi Model View Presenter di Android

Scroll to top
Read Time: 12 min
This post is part of a series called How to Adopt Model View Presenter on Android.
An Introduction to Model View Presenter on Android
Testing and Dependency Injection With Model View Presenter on Android

() translation by (you can also view the original English article)

Pada tutorial sebelumnya, kita berbicara tentang pola Model View Presenter, bagaimana penerapannya di Android, dan apa keuntungan terpentingnya. Dalam tutorial ini, kita mengeksplorasi pola Model View Presenter secara lebih rinci dengan menerapkannya di aplikasi Android.

Dalam tutorial ini:

  • kita membangun aplikasi sederhana dengan menggunakan pola MVP
  • kita mengeksplorasi bagaimana menerapkan pola MVP di Android
  • dan kita membahas bagaimana mengatasi beberapa kesulitan yang disebabkan oleh arsitektur Android

1. Model View Presenter

Pola Model View Presenter adalah pola arsitektural yang didasarkan pada pola Model View Controller (MVC) yang meningkatkan pemisahan kekhawatiran dan memfasilitasi pengujian unit. Ini menciptakan tiga lapisan, Model, View, dan Presenter, masing-masing memiliki tanggung jawab yang jelas.

Model View Presenter LayersModel View Presenter LayersModel View Presenter Layers

Model memegang logika bisnis aplikasi. Ini mengontrol bagaimana data dibuat, disimpan, dan dimodifikasi. View adalah antarmuka pasif yang menampilkan data dan mengarahkan tindakan pengguna ke Presenter. Presenter bertindak sebagai perantara. Ini mengambil data dari Model dan menampilkannya di View. Ini juga memproses tindakan pengguna yang diteruskan oleh View.

2. Perencanaan Proyek & Penyiapan

Kita akan membuat aplikasi catatan sederhana untuk menggambarkan MVP. Aplikasi ini memungkinkan pengguna mencatat, menyimpannya di database lokal, dan menghapus catatan. Untuk membuatnya sederhana, aplikasi hanya akan memiliki satu Activity.

App LayoutApp LayoutApp Layout

Layout AplikasiDalam tutorial ini, kita berkonsentrasi terutama pada implementasi pola MVP. Fungsi lainnya, seperti menyiapkan database SQLite, membangun DAO, atau menangani interaksi pengguna, dilewati. Jika Anda memerlukan bantuan untuk topik ini, Envato Tuts+ memiliki beberapa tutorial hebat tentang topik ini.

Diagram Action dan Lapisan MVP

Mari kita mulai dengan membuat catatan baru. Jika kita memecah tindakan ini menjadi operasi-operasi yang lebih kecil, maka inilah yang akan terlihat seperti menggunakan pola arsitektur MVP:

  • Pengguna mengetik catatan dan mengklik tombol tambah catatan.
  • Presenter membuat objek Note dengan teks yang dimasukkan oleh pengguna dan meminta Model untuk memasukkannya ke dalam database.
  • Model memasukkan catatan di database dan menginformasikan kepada Presenter bahwa daftar catatan telah berubah.
  • Presenter membersihkan field teks dan meminta View untuk menyegarkan daftar untuk menunjukkan catatan yang baru dibuat.
MVP Action DiagramMVP Action DiagramMVP Action Diagram

Antarmuka MVP

Sekarang mari kita pertimbangkan operasi yang dibutuhkan untuk mencapai tindakan ini dan memisahkan dengan menggunakan MVP. Untuk menjaga agar berbagai objek digabungkan secara longgar, komunikasi antar lapisan terjadi dengan menggunakan antarmuka. Kita membutuhkan empat antarmuka:

  • RequiredViewOps: diperlukan operasi View yang tersedia untuk Presenter
  • ProvidedPresenterOps: operasi ditawarkan ke View untuk berkomunikasi dengan Presenter
  • RequiredPresenterOps: diperlukan operasi Presenter yang tersedia untuk Model
  • ProvidedModelOps: operasi ditawarkan ke Model untuk berkomunikasi dengan Presenter
MVP InterfacesMVP InterfacesMVP Interfaces

3. Menerapkan MVP di Android

Sekarang kita memiliki gagasan tentang bagaimana berbagai metode harus diatur, kita dapat mulai membuat aplikasi kita. Kita menyederhanakan pelaksanaannya dengan hanya berfokus pada tindakan untuk menambahkan catatan baru. File sumber dari tutorial ini tersedia di GitHub.

Kita hanya menggunakan satu Activity dengan tata letak yang meliputi:

  • EditText untuk catatan baru
  • Button untuk menambahkan catatan
  • RecyclerView untuk mencantumkan semua catatan
  • dua elemen TextView dan Button di dalam pemegang RecyclerView

Antarmuka

Mari kita mulai dengan membuat antarmukanya. Agar semuanya tetap teratur, kami menempatkan antarmuka di dalam holder. Sekali lagi, dalam contoh ini kita fokus pada tindakan untuk menambahkan catatan baru.

1
public interface MVP_Main {
2
    /**

3
     * Required View methods available to Presenter.

4
     * A passive layer, responsible to show data

5
     * and receive user interactions

6
     */
7
    interface RequiredViewOps {
8
      // View operations permitted to Presenter

9
		Context getAppContext();
10
        	Context getActivityContext();
11
		void notifyItemInserted(int layoutPosition);
12
        	void notifyItemRangeChanged(int positionStart, int itemCount);
13
    }
14
15
    /**

16
     * Operations offered to View to communicate with Presenter.

17
     * Processes user interactions, sends data requests to Model, etc.

18
     */
19
    interface ProvidedPresenterOps {
20
       	// Presenter operations permitted to View

21
		void clickNewNote(EditText editText);
22
            // setting up recycler adapter

23
            int getNotesCount();
24
            NotesViewHolder createViewHolder(ViewGroup parent, int viewType);
25
            void bindViewHolder(NotesViewHolder holder, int position);
26
    }
27
28
    /**

29
     * Required Presenter methods available to Model.

30
     */
31
    interface RequiredPresenterOps {
32
       	// Presenter operations permitted to Model

33
		Context getAppContext();
34
        	Context getActivityContext();
35
    }
36
37
    /**

38
     * Operations offered to Model to communicate with Presenter

39
     * Handles all data business logic.

40
     */
41
    interface ProvidedModelOps {
42
        	// Model operations permitted to Presenter

43
            int getNotesCount();
44
            Note getNote(int position);
45
		int insertNote(Note note);
46
        	boolean loadData();
47
    }
48
}

Lapisan View

Sekarang saatnya membuat lapisan Model, View, dan Presenter. Karena MainActivity akan bertindak sebagai View, seharusnya mengimplementasikan antarmuka RequiredViewOps.

1
public class MainActivity
2
        extends AppCompatActivity
3
    implements View.OnClickListener, MVP_Main.RequiredViewOps {
4
5
    private MVP_Main.ProvidedPresenterOps mPresenter;
6
    private EditText mTextNewNote;
7
    private ListNotes mListAdapter;
8
9
    @Override
10
    public void onClick(View v) {
11
        switch (v.getId()) {
12
            case R.id.fab:{
13
                // Adds a new note

14
                mPresenter.clickNewNote(mTextNewNote);
15
            }
16
        }
17
    }
18
    @Override
19
    public Context getActivityContext() {
20
        return this;
21
    }
22
23
    @Override
24
    public Context getAppContext() {
25
        return getApplicationContext();
26
    }
27
    // Notify the RecyclerAdapter that a new item was inserted

28
    @Override
29
    public void notifyItemInserted(int adapterPos) {
30
        mListAdapter.notifyItemInserted(adapterPos);
31
    }
32
    // notify the RecyclerAdapter that items has changed

33
    @Override
34
    public void notifyItemRangeChanged(int positionStart, int itemCount){
35
        mListAdapter.notifyItemRangeChanged(positionStart, itemCount);
36
    }
37
    // notify the RecyclerAdapter that data set has changed

38
    @Override
39
    public void notifyDataSetChanged() {
40
        mListAdapter.notifyDataSetChanged();
41
    }
42
    // Recycler adapter

43
    // This class could have their own Presenter, but for the sake of

44
    // simplicity, will use only one Presenter.

45
    // The adapter is passive and all the processing occurs 

46
    // in the Presenter layer.

47
    private class ListNotes extends RecyclerView.Adapter<NotesViewHolder>       
48
    {
49
        @Override
50
        public int getItemCount() {
51
            return mPresenter.getNotesCount();
52
        }
53
54
        @Override
55
        public NotesViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
56
            return mPresenter.createViewHolder(parent, viewType);
57
        }
58
59
        @Override
60
        public void onBindViewHolder(NotesViewHolder holder, int position) {
61
            mPresenter.bindViewHolder(holder, position);
62
        }
63
    }
64
}

Lapisan Presenter

Presenter adalah perantara dan perlu menerapkan dua antarmuka:

  • ProvidedPresenterOps untuk mengizinkan panggilan dari View
  • RequiredPresenterOps untuk menerima hasil dari Model

Beri perhatian khusus pada referensi lapisan View. Kita perlu menggunakan WeakReference<MVP_Main.RequiredViewOps> karena MainActivity dapat dihancurkan kapan saja dan kita ingin menghindari kebocoran memori. Juga, lapisan Model belum disiapkan. Kita melakukannya nanti saat kita menghubungkan lapisan MVP bersama-sama.

1
public class MainPresenter implements MVP_Main.ProvidedPresenterOps, MVP_Main.RequiredPresenterOps {
2
3
    // View reference. We use as a WeakReference

4
    // because the Activity could be destroyed at any time

5
    // and we don't want to create a memory leak

6
    private WeakReference<MVP_Main.RequiredViewOps> mView;
7
    // Model reference

8
    private MVP_Main.ProvidedModelOps mModel;
9
10
    /**

11
     * Presenter Constructor

12
     * @param view  MainActivity

13
     */
14
    public MainPresenter(MVP_Main.RequiredViewOps view) {
15
        mView = new WeakReference<>(view);
16
    }
17
18
    /**

19
     * Return the View reference.

20
     * Throw an exception if the View is unavailable.

21
     */
22
    private MVP_Main.RequiredViewOps getView() throws NullPointerException{
23
        if ( mView != null )
24
            return mView.get();
25
        else
26
            throw new NullPointerException("View is unavailable");
27
    }
28
29
    /**

30
     * Retrieves total Notes count from Model

31
     * @return  Notes list size

32
     */
33
    @Override
34
    public int getNotesCount() {
35
        return mModel.getNotesCount();
36
    }
37
38
    /**

39
     * Creates the RecyclerView holder and setup its view

40
     * @param parent    Recycler viewGroup

41
     * @param viewType  Holder type

42
     * @return          Recycler ViewHolder

43
     */
44
    @Override
45
    public NotesViewHolder createViewHolder(ViewGroup parent, int viewType) {
46
        NotesViewHolder viewHolder;
47
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
48
49
        View viewTaskRow = inflater.inflate(R.layout.holder_notes, parent, false);
50
        viewHolder = new NotesViewHolder(viewTaskRow);
51
52
        return viewHolder;
53
    }
54
55
    /**

56
     * Binds ViewHolder with RecyclerView

57
     * @param holder    Holder to bind

58
     * @param position  Position on Recycler adapter

59
     */
60
    @Override
61
    public void bindViewHolder(final NotesViewHolder holder, int position) {
62
        final Note note = mModel.getNote(position);
63
        holder.text.setText( note.getText() );
64
        holder.date.setText( note.getDate() );
65
        holder.btnDelete.setOnClickListener(new View.OnClickListener() {
66
            @Override
67
            public void onClick(View v) {
68
                clickDeleteNote(note, holder.getAdapterPosition(), holder.getLayoutPosition());
69
            }
70
        });
71
72
    }
73
74
    /**

75
     * @return  Application context

76
     */
77
    @Override
78
    public Context getAppContext() {
79
        try {
80
            return getView().getAppContext();
81
        } catch (NullPointerException e) {
82
            return null;
83
        }
84
    }
85
86
    /**

87
     * @return  Activity context

88
     */
89
    @Override
90
    public Context getActivityContext() {
91
        try {
92
            return getView().getActivityContext();
93
        } catch (NullPointerException e) {
94
            return null;
95
        }
96
    }
97
98
    /**

99
     * Called by View when user clicks on new Note button.

100
     * Creates a Note with text typed by the user and asks

101
     * Model to insert it in DB.

102
     * @param editText  EditText with text typed by user

103
     */
104
    @Override
105
    public void clickNewNote(final EditText editText) {
106
        getView().showProgress();
107
        final String noteText = editText.getText().toString();
108
        if ( !noteText.isEmpty() ) {
109
            new AsyncTask<Void, Void, Integer>() {
110
                @Override
111
                protected Integer doInBackground(Void... params) {
112
                    // Inserts note in Model, returning adapter position

113
                    return mModel.insertNote(makeNote(noteText));
114
                }
115
116
                @Override
117
                protected void onPostExecute(Integer adapterPosition) {
118
                    try {
119
                        if (adapterPosition > -1) {
120
                            // Note inserted

121
                            getView().clearEditText();
122
                            getView().notifyItemInserted(adapterPosition + 1);
123
                            getView().notifyItemRangeChanged(adapterPosition, mModel.getNotesCount());
124
                        } else {
125
                            // Informs about error

126
                            getView().hideProgress();
127
                            getView().showToast(makeToast("Error creating note [" + noteText + "]"));
128
                        }
129
                    } catch (NullPointerException e) {
130
                        e.printStackTrace();
131
                    }
132
                }
133
            }.execute();
134
        } else {
135
            try {
136
                getView().showToast(makeToast("Cannot add a blank note!"));
137
            } catch (NullPointerException e) {
138
                e.printStackTrace();
139
            }
140
        }
141
    }
142
143
    /**

144
     * Creates a Note object with given text

145
     * @param noteText  String with Note text

146
     * @return  A Note object

147
     */
148
    public Note makeNote(String noteText) {
149
        Note note = new Note();
150
        note.setText( noteText );
151
        note.setDate(getDate());
152
        return note;
153
154
    }
155
}

Lapisan Model

Lapisan Model bertanggung jawab untuk menangani logika bisnis. Ini memegang ArrayList dengan catatan yang ditambahkan ke database, referensi DAO untuk melakukan operasi database, dan referensi ke Presenter.

1
public class MainModel implements MVP_Main.ProvidedModelOps {
2
3
    // Presenter reference

4
    private MVP_Main.RequiredPresenterOps mPresenter;
5
    private DAO mDAO;
6
    // Recycler data

7
    public ArrayList<Note> mNotes;
8
9
    /**

10
     * Main constructor, called by Activity during MVP setup

11
     * @param presenter Presenter instance

12
     */
13
    public MainModel(MVP_Main.RequiredPresenterOps presenter) {
14
        this.mPresenter = presenter;
15
        mDAO = new DAO( mPresenter.getAppContext() );
16
    }
17
18
     /**

19
     * Inserts a note on DB

20
     * @param note  Note to insert

21
     * @return      Note's position on ArrayList

22
     */
23
    @Override
24
    public int insertNote(Note note) {
25
        Note insertedNote = mDAO.insertNote(note);
26
        if ( insertedNote != null ) {
27
            loadData();
28
            return getNotePosition(insertedNote);
29
        }
30
        return -1;
31
    }
32
33
     /**

34
     * Loads all Data, getting notes from DB

35
     * @return  true with success

36
     */
37
    @Override
38
    public boolean loadData() {
39
        mNotes = mDAO.getAllNotes();
40
        return mNotes != null;
41
    }
42
     /**

43
     * Gets a specific note from notes list using its array position

44
     * @param position    Array position

45
     * @return            Note from list

46
     */
47
    @Override
48
    public Note getNote(int position) {
49
        return mNotes.get(position);
50
    }
51
     
52
    /**

53
     * Get ArrayList size

54
     * @return  ArrayList size

55
     */
56
    @Override
57
    public int getNotesCount() {
58
        if ( mNotes != null )
59
            return mNotes.size();
60
        return 0;
61
    }
62
}

4. Mengikat Segalanya Bersama-sama

Dengan lapisan MVP di tempat, kita perlu memberi instantiate dan memasukkan referensi yang diperlukan. Sebelum kita melakukannya, kita perlu membahas beberapa masalah yang terkait langsung dengan Android.

Instansiasi Lapisan-lapisannya

Karena Android tidak mengizinkan instansiasi Activity, lapisan View akan diinstansiasikan untuk kita. Kita bertanggung jawab untuk instansiasi lapisan Presenter dan Model. Sayangnya, instansiasi lapisan di luar Activity bisa menjadi masalah.

Dianjurkan untuk menggunakan bentuk injeksi ketergantungan untuk mencapai hal ini. Karena tujuan kita adalah berkonsentrasi pada implementasi MVP, kita akan mengambil pendekatan yang lebih mudah. Ini bukan pendekatan terbaik yang tersedia, tapi ini yang paling mudah dipahami. Kita akan membahas MVP dan injeksi ketergantungan nanti di serial ini.

  • instansiasi Presenter dan Model dalam Activity menggunakan variabel lokal
  • atur RequiredViewOps dan ProvidedModelOps di Presenter
  • atur RequiredPresenterOps di Model
  • simpan ProvidedPresenterOps sebagai referensi untuk digunakan di View
1
/**

2
* Setup Model View Presenter pattern

3
*/
4
private void setupMVP() {
5
     // Create the Presenter

6
     MainPresenter presenter = new MainPresenter(this);
7
     // Create the Model

8
     MainModel model = new MainModel(presenter);
9
     // Set Presenter model

10
     presenter.setModel(model);
11
     // Set the Presenter as a interface

12
     mPresenter = presenter;
13
}

Menangani Perubahan Konfigurasi

Hal lain yang harus kita pertimbangkan adalah siklus hidup Activity. Activity Android dapat dihancurkan setiap saat dan lapisan Presenter dan Model juga bisa dihancurkan dengannya. Kita perlu memperbaikinya dengan menggunakan beberapa jenis keadaan mesin untuk menyimpan keadaan selama perubahan konfigurasi. Kita juga harus menginformasikan lapisan lain tentang status Activity.

Untuk mencapai hal ini, kita akan menggunakan kelas terpisah, StateMaintainer, yang berisi sebuah Fragment yang mempertahankan kondisinya dan menggunakan fragmen ini untuk menyimpan dan mengambil objek kita. Anda bisa melihat implementasi kelas ini di file sumber tutorial ini.

Kita perlu menambahkan metode onDestroy ke Presenter dan Model untuk memberi tahu mereka tentang status Activity saat ini. Kita juga perlu menambahkan metode setView ke Presenter, yang akan bertanggung jawab untuk menerima referensi View baru dari Activity yang diciptakan kembali.

View LifecycleView LifecycleView Lifecycle
1
public class MainActivity
2
        extends AppCompatActivity
3
    implements View.OnClickListener, MVP_Main.RequiredViewOps
4
{
5
    // …

6
    private void setupMVP() {
7
        // Check if StateMaintainer has been created

8
        if (mStateMaintainer.firstTimeIn()) {
9
            // Create the Presenter

10
            MainPresenter presenter = new MainPresenter(this);
11
            // Create the Model

12
            MainModel model = new MainModel(presenter);
13
            // Set Presenter model

14
            presenter.setModel(model);
15
            // Add Presenter and Model to StateMaintainer

16
            mStateMaintainer.put(presenter);
17
            mStateMaintainer.put(model);
18
19
            // Set the Presenter as a interface

20
            // To limit the communication with it

21
            mPresenter = presenter;
22
23
        }
24
        // get the Presenter from StateMaintainer

25
        else {
26
            // Get the Presenter

27
            mPresenter = mStateMaintainer.get(MainPresenter.class.getName());
28
            // Updated the View in Presenter

29
            mPresenter.setView(this);
30
        }
31
    }
32
    // …

33
}

Kesimpulan

Pola MVP mampu memecahkan beberapa masalah yang disebabkan oleh arsitektur bawaan Android. Itu membuat kode Anda mudah dipelihara dan diuji. Mengadopsi MVP mungkin terlihat sulit pada awalnya, tapi begitu Anda memahami logika di baliknya, keseluruhan proses itu mudah.

Anda sekarang bisa membuat perpustakaan MVP sendiri atau menggunakan solusi yang sudah tersedia, seperti Mosby atau simple-mvp. Anda sekarang seharusnya lebih memahami apa yang perpustakaan ini lakukan di belakang layar.

Kita hampir di akhir perjalanan MVP kita. Pada bagian ketiga dan terakhir dari seri ini, kami akan menambahkan pengujian unit ke dalam campuran dan menyesuaikan kode kita untuk menggunakan injeksi ketergantungan dengan Dagger. Saya berharap bisa bertemu Anda di sana.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.