├── LICENSE
├── README.md
└── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
├── androidTest
└── java
│ └── com
│ └── tinmegali
│ └── tutsmvp_sample
│ └── ApplicationTest.java
├── main
├── AndroidManifest.xml
├── java
│ └── com
│ │ └── tinmegali
│ │ └── tutsmvp_sample
│ │ ├── common
│ │ └── StateMaintainer.java
│ │ ├── data
│ │ ├── DAO.java
│ │ └── DBSchema.java
│ │ ├── main
│ │ └── activity
│ │ │ ├── MVP_Main.java
│ │ │ ├── model
│ │ │ └── MainModel.java
│ │ │ ├── presenter
│ │ │ └── MainPresenter.java
│ │ │ └── view
│ │ │ ├── MainActivity.java
│ │ │ └── recycler
│ │ │ └── NotesViewHolder.java
│ │ └── models
│ │ └── Note.java
└── res
│ ├── drawable-hdpi
│ ├── ic_action_add.png
│ └── ic_action_cancel.png
│ ├── drawable-ldpi
│ ├── ic_action_add.png
│ └── ic_action_cancel.png
│ ├── drawable-mdpi
│ ├── ic_action_add.png
│ └── ic_action_cancel.png
│ ├── drawable-tvdpi
│ ├── ic_action_add.png
│ └── ic_action_cancel.png
│ ├── drawable-xhdpi
│ ├── ic_action_add.png
│ └── ic_action_cancel.png
│ ├── drawable-xxhdpi
│ ├── ic_action_add.png
│ └── ic_action_cancel.png
│ ├── drawable-xxxhdpi
│ ├── ic_action_add.png
│ └── ic_action_cancel.png
│ ├── layout
│ ├── activity_main.xml
│ ├── content_main.xml
│ └── holder_notes.xml
│ ├── menu
│ └── menu_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-v21
│ └── styles.xml
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
└── test
└── java
└── com
└── tinmegali
└── tutsmvp_sample
├── DBTest.java
├── MainModelTest.java
└── MainPresenterTest.java
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016, Envato Tuts+
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Envato Tuts+ Tutorial: How to Adopt Model View Presenter on Android
2 |
3 | #### Instructor: Tin Megali
4 |
5 | In the previous tutorial, we talked about the Model View Presenter pattern, how it is applied on Android, and what its most important advantages are. In this tutorial, we explore the Model View Presenter pattern in more detail by implementing it in an Android application.
6 |
7 | Source files for the Envato Tuts+ tutorial: [How to Adopt Model View Presenter on Android](http://code.tutsplus.com/tutorials/how-to-adopt-model-view-presenter-on-android--cms-26206)
8 |
9 | **Read this tutorial on [Envato Tuts+](https://code.tutsplus.com)**.
10 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.2"
6 |
7 | defaultConfig {
8 | applicationId "com.tinmegali.tutsmvp_sample"
9 | minSdkVersion 19
10 | targetSdkVersion 23
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | testCompile 'junit:junit:4.12'
25 | // Set this dependency if you want to use Hamcrest matching
26 | testCompile 'org.hamcrest:hamcrest-library:1.1'
27 | testCompile "org.robolectric:robolectric:3.0"
28 | testCompile 'org.mockito:mockito-core:1.10.19'
29 |
30 | compile 'com.android.support:appcompat-v7:23.2.0'
31 | compile 'com.android.support:design:23.2.0'
32 | }
33 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/tinmegali/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/tinmegali/tutsmvp_sample/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.tinmegali.tutsmvp_sample;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tinmegali/tutsmvp_sample/common/StateMaintainer.java:
--------------------------------------------------------------------------------
1 | package com.tinmegali.tutsmvp_sample.common;
2 |
3 | /**
4 | * ---------------------------------------------------
5 | * Created by Tin Megali on 19/03/16.
6 | * Project: tuts+mvp_sample
7 | * ---------------------------------------------------
8 | * tinmegali.com
9 | * www.tinmegali.com
29 | * Based on
30 | * framework MVP developed by
31 | *
32 | * Dr. Douglas Schmidth
33 | *
34 | * @see Project's Git
35 | * @see Sample Application
36 | * @see
37 | * Sample MVP interface
38 | *
39 | */
40 | public class StateMaintainer {
41 |
42 | protected final String TAG = getClass().getSimpleName();
43 |
44 | private final String mStateMaintenerTag;
45 | private final WeakReference mFragmentManager;
46 | private StateMngFragment mStateMaintainerFrag;
47 | private boolean mIsRecreating;
48 |
49 | /**
50 | * Constructor
51 | */
52 | public StateMaintainer(FragmentManager fragmentManager, String stateMaintainerTAG) {
53 | mFragmentManager = new WeakReference<>(fragmentManager);
54 | mStateMaintenerTag = stateMaintainerTAG;
55 | }
56 |
57 | /**
58 | * Creates the Fragment responsible to maintain the objects.
59 | * @return true: fragment just created
60 | */
61 | public boolean firstTimeIn() {
62 | try {
63 | // Recuperando referência
64 | mStateMaintainerFrag = (StateMngFragment)
65 | mFragmentManager.get().findFragmentByTag(mStateMaintenerTag);
66 |
67 | // Criando novo RetainedFragment
68 | if (mStateMaintainerFrag == null) {
69 | Log.d(TAG, "Criando novo RetainedFragment " + mStateMaintenerTag);
70 | mStateMaintainerFrag = new StateMngFragment();
71 | mFragmentManager.get().beginTransaction()
72 | .add(mStateMaintainerFrag, mStateMaintenerTag).commit();
73 | mIsRecreating = false;
74 | return true;
75 | } else {
76 | Log.d(TAG, "Retornando retained fragment existente " + mStateMaintenerTag);
77 | mIsRecreating = true;
78 | return false;
79 | }
80 | } catch (NullPointerException e) {
81 | Log.w(TAG, "Erro firstTimeIn()");
82 | return false;
83 | }
84 | }
85 |
86 | /**
87 | * Return true it the current Activity was recreated at least one time
88 | * @return If the Activity was recreated
89 | */
90 | public boolean wasRecreated() { return mIsRecreating; }
91 |
92 |
93 | /**
94 | * Insert the object to be preserved.
95 | * @param key object's TAG
96 | * @param obj object to maintain
97 | */
98 | public void put(String key, Object obj) {
99 | mStateMaintainerFrag.put(key, obj);
100 | }
101 |
102 | /**
103 | * Insert the object to be preserved.
104 | * @param obj object to maintain
105 | */
106 | public void put(Object obj) {
107 | put(obj.getClass().getName(), obj);
108 | }
109 |
110 |
111 | /**
112 | * Recovers the object saved
113 | * @param key Object's TAG
114 | * @param Object type
115 | * @return Object saved
116 | */
117 | @SuppressWarnings("unchecked")
118 | public T get(String key) {
119 | return mStateMaintainerFrag.get(key);
120 |
121 | }
122 |
123 | /**
124 | * Checks the existence of a given object
125 | * @param key Key to verification
126 | * @return true: Object exists
127 | */
128 | public boolean hasKey(String key) {
129 | return mStateMaintainerFrag.get(key) != null;
130 | }
131 |
132 |
133 | /**
134 | * Fragment resposible to preserve objects.
135 | * Instantiated only once. Uses a hashmap to save objs
136 | */
137 | public static class StateMngFragment extends Fragment {
138 | private HashMap mData = new HashMap<>();
139 |
140 | @Override
141 | public void onCreate(Bundle savedInstanceState) {
142 | super.onCreate(savedInstanceState);
143 | // Grants that the fragment will be preserved
144 | setRetainInstance(true);
145 | }
146 |
147 | /**
148 | * Insert objects on the hashmap
149 | * @param key Reference key
150 | * @param obj Object to be saved
151 | */
152 | public void put(String key, Object obj) {
153 | mData.put(key, obj);
154 | }
155 |
156 | /**
157 | * Insert objects on the hashmap
158 | * @param object Object to be saved
159 | */
160 | public void put(Object object) {
161 | put(object.getClass().getName(), object);
162 | }
163 |
164 | /**
165 | * Recovers saved object
166 | * @param key Reference key
167 | * @param Object type
168 | * @return Object saved
169 | */
170 | @SuppressWarnings("unchecked")
171 | public T get(String key) {
172 | return (T) mData.get(key);
173 | }
174 | }
175 |
176 | }
177 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tinmegali/tutsmvp_sample/data/DAO.java:
--------------------------------------------------------------------------------
1 | package com.tinmegali.tutsmvp_sample.data;
2 |
3 | import android.content.Context;
4 | import android.database.Cursor;
5 | import android.database.sqlite.SQLiteDatabase;
6 |
7 | import com.tinmegali.tutsmvp_sample.models.Note;
8 |
9 | import java.util.ArrayList;
10 |
11 | /**
12 | * ---------------------------------------------------
13 | * Created by Tin Megali on 18/03/16.
14 | * Project: tuts+mvp_sample
15 | * ---------------------------------------------------
16 | * tinmegali.com
17 | * getAllNotes() {
68 | SQLiteDatabase db = getReadDB();
69 | Cursor c = db.query(
70 | DBSchema.TABLE_NOTES,
71 | null,
72 | null,
73 | null, null, null,
74 | SORT_ORDER_DEFAULT
75 | );
76 | if ( c!= null) {
77 | c.moveToFirst();
78 | ArrayList notes = new ArrayList<>();
79 | while (!c.isAfterLast()) {
80 | Note note = new Note();
81 | note.setId( c.getInt( c.getColumnIndexOrThrow( DBSchema.TB_NOTES.ID )));
82 | note.setText(c.getString(c.getColumnIndexOrThrow(DBSchema.TB_NOTES.NOTE)));
83 | note.setDate(c.getString(c.getColumnIndexOrThrow(DBSchema.TB_NOTES.DATE)));
84 | notes.add(note);
85 | c.moveToNext();
86 | }
87 | c.close();
88 | db.close();
89 | return notes;
90 | } else {
91 | return null;
92 | }
93 | }
94 |
95 | public Note getNote(int id){
96 | SQLiteDatabase db = getReadDB();
97 | Cursor c = db.query(
98 | DBSchema.TABLE_NOTES,
99 | null,
100 | SELECT_ID_BASED,
101 | new String[]{Integer.toString(id)},
102 | null,
103 | null,
104 | null
105 | );
106 | if (c != null) {
107 | c.moveToFirst();
108 | Note note = new Note();
109 | note.setId(c.getInt(c.getColumnIndexOrThrow(DBSchema.TB_NOTES.ID)));
110 | note.setText(c.getString(c.getColumnIndexOrThrow(DBSchema.TB_NOTES.NOTE)));
111 | note.setDate(c.getString(c.getColumnIndexOrThrow(DBSchema.TB_NOTES.DATE)));
112 | c.close();
113 | db.close();
114 | return note;
115 | } else return null;
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tinmegali/tutsmvp_sample/data/DBSchema.java:
--------------------------------------------------------------------------------
1 | package com.tinmegali.tutsmvp_sample.data;
2 |
3 | import android.content.Context;
4 | import android.database.sqlite.SQLiteDatabase;
5 | import android.database.sqlite.SQLiteOpenHelper;
6 |
7 | /**
8 | * ---------------------------------------------------
9 | * Created by Tin Megali on 18/03/16.
10 | * Project: tuts+mvp_sample
11 | * ---------------------------------------------------
12 | * tinmegali.com
13 | * tinmegali.com
28 | * tinmegali.com
17 | * tinmegali.com
32 | * (view);
91 | }
92 |
93 | /**
94 | * Called by Activity during MVP setup. Only called once.
95 | * @param model Model instance
96 | */
97 | public void setModel(MVP_Main.ProvidedModelOps model) {
98 | mModel = model;
99 |
100 | // start to load data
101 | loadData();
102 | }
103 |
104 | /**
105 | * Load data from Model in a AsyncTask
106 | */
107 | private void loadData() {
108 | try {
109 | getView().showProgress();
110 | new AsyncTask() {
111 | @Override
112 | protected Boolean doInBackground(Void... params) {
113 | // Load data from Model
114 | return mModel.loadData();
115 | }
116 |
117 | @Override
118 | protected void onPostExecute(Boolean result) {
119 | try {
120 | getView().hideProgress();
121 | if (!result) // Loading error
122 | getView().showToast(makeToast("Error loading data."));
123 | else // success
124 | getView().notifyDataSetChanged();
125 | } catch (NullPointerException e) {
126 | e.printStackTrace();
127 | }
128 | }
129 | }.execute();
130 | } catch (NullPointerException e) {
131 | e.printStackTrace();
132 | }
133 | }
134 |
135 | /**
136 | * Creat a Toast object with given message
137 | * @param msg Toast message
138 | * @return A Toast object
139 | */
140 | private Toast makeToast(String msg) {
141 | return Toast.makeText(getView().getAppContext(), msg, Toast.LENGTH_SHORT);
142 | }
143 |
144 | /**
145 | * Retrieve total Notes count from Model
146 | * @return Notes size
147 | */
148 | @Override
149 | public int getNotesCount() {
150 | return mModel.getNotesCount();
151 | }
152 |
153 | /**
154 | * Create the RecyclerView holder and setup its view
155 | * @param parent Recycler viewgroup
156 | * @param viewType Holder type
157 | * @return Recycler ViewHolder
158 | */
159 | @Override
160 | public NotesViewHolder createViewHolder(ViewGroup parent, int viewType) {
161 | NotesViewHolder viewHolder;
162 | LayoutInflater inflater = LayoutInflater.from(parent.getContext());
163 |
164 | View viewTaskRow = inflater.inflate(R.layout.holder_notes, parent, false);
165 | viewHolder = new NotesViewHolder(viewTaskRow);
166 |
167 | return viewHolder;
168 | }
169 |
170 | /**
171 | * Binds ViewHolder with RecyclerView
172 | * @param holder Holder to bind
173 | * @param position Position on Recycler adapter
174 | */
175 | @Override
176 | public void bindViewHolder(final NotesViewHolder holder, int position) {
177 | final Note note = mModel.getNote(position);
178 | holder.text.setText( note.getText() );
179 | holder.date.setText( note.getDate() );
180 | holder.btnDelete.setOnClickListener(new View.OnClickListener() {
181 | @Override
182 | public void onClick(View v) {
183 | clickDeleteNote(note, holder.getAdapterPosition(), holder.getLayoutPosition());
184 | }
185 | });
186 |
187 | }
188 |
189 | /**
190 | * Retrieve Application Context
191 | * @return Application context
192 | */
193 | @Override
194 | public Context getAppContext() {
195 | try {
196 | return getView().getAppContext();
197 | } catch (NullPointerException e) {
198 | return null;
199 | }
200 | }
201 |
202 | /**
203 | * Retrieves Activity context
204 | * @return Activity context
205 | */
206 | @Override
207 | public Context getActivityContext() {
208 | try {
209 | return getView().getActivityContext();
210 | } catch (NullPointerException e) {
211 | return null;
212 | }
213 | }
214 |
215 | /**
216 | * Called by View when user clicks on new Note btn.
217 | * Creates a Note with text typed by the user and asks
218 | * Model to insert in DB.
219 | * @param editText EdiText with text typed by user
220 | */
221 | @Override
222 | public void clickNewNote(final EditText editText) {
223 | getView().showProgress();
224 | final String noteText = editText.getText().toString();
225 | if ( !noteText.isEmpty() ) {
226 | new AsyncTask() {
227 | @Override
228 | protected Integer doInBackground(Void... params) {
229 | // Insert note in Model, returning result
230 | return mModel.insertNote(makeNote(noteText));
231 | }
232 |
233 | @Override
234 | protected void onPostExecute(Integer adapterPosition) {
235 | try {
236 | if (adapterPosition > -1) {
237 | // Insert note
238 | getView().clearEditText();
239 | getView().notifyItemInserted(adapterPosition + 1);
240 | getView().notifyItemRangeChanged(adapterPosition, mModel.getNotesCount());
241 | } else {
242 | // Inform about error
243 | getView().hideProgress();
244 | getView().showToast(makeToast("Error creating note [" + noteText + "]"));
245 | }
246 | } catch (NullPointerException e) {
247 | e.printStackTrace();
248 | }
249 | }
250 | }.execute();
251 | } else {
252 | try {
253 | getView().showToast(makeToast("Cannot add a blank note!"));
254 | } catch (NullPointerException e) {
255 | e.printStackTrace();
256 | }
257 | }
258 | }
259 |
260 | /**
261 | * Create a Note object with giver text
262 | * @param noteText String with Note text
263 | * @return A Note object
264 | */
265 | public Note makeNote(String noteText) {
266 | Note note = new Note();
267 | note.setText( noteText );
268 | note.setDate(getDate());
269 | return note;
270 |
271 | }
272 |
273 | /**
274 | * Get current Date as a String
275 | * @return The current date
276 | */
277 | private String getDate() {
278 | return new SimpleDateFormat("HH:mm:ss - MM/dd/yyyy", Locale.getDefault()).format(new Date());
279 | }
280 |
281 | /**
282 | * Called by View when user click on delete button.
283 | * Create a AlertBox to confirm the action.
284 | * @param note Note to delete
285 | * @param adapterPos Position on adapter
286 | * @param layoutPos Layout position on RecyclerView
287 | */
288 | @Override
289 | public void clickDeleteNote(final Note note, final int adapterPos, final int layoutPos) {
290 | openDeleteAlert(note, adapterPos, layoutPos);
291 | }
292 |
293 | /**
294 | * Create an AlertBox to confirm a delete action
295 | * @param note Note to be deleted
296 | * @param adapterPos Adapter postion
297 | * @param layoutPos Recycler layout position
298 | */
299 | private void openDeleteAlert(final Note note, final int adapterPos, final int layoutPos){
300 | AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivityContext());
301 | alertBuilder.setPositiveButton("DELETE", new DialogInterface.OnClickListener() {
302 | @Override
303 | public void onClick(DialogInterface dialog, int which) {
304 | // Delete note if action is confirmed
305 | deleteNote(note, adapterPos, layoutPos);
306 | }
307 | });
308 | alertBuilder.setNegativeButton("CANCEL", new DialogInterface.OnClickListener() {
309 | @Override
310 | public void onClick(DialogInterface dialog, int which) {
311 | dialog.dismiss();
312 | }
313 | });
314 | alertBuilder.setTitle("Delete Note");
315 | alertBuilder.setMessage("Delete " + note.getText() + " ?");
316 |
317 | AlertDialog alertDialog = alertBuilder.create();
318 | try {
319 | getView().showAlert(alertDialog);
320 | } catch (NullPointerException e) {
321 | e.printStackTrace();
322 | }
323 | }
324 |
325 | /**
326 | * Create a asyncTask to delete the object in Model
327 | * @param note Note to delete
328 | * @param adapterPos Adapter position
329 | * @param layoutPos Recycler layout position
330 | */
331 | public void deleteNote(final Note note, final int adapterPos, final int layoutPos) {
332 | getView().showProgress();
333 | new AsyncTask() {
334 | @Override
335 | protected Boolean doInBackground(Void... params) {
336 | // Delete note on Model, returning result
337 | return mModel.deleteNote(note, adapterPos);
338 | }
339 |
340 | @Override
341 | protected void onPostExecute(Boolean result) {
342 | try {
343 | getView().hideProgress();
344 | if ( result ) {
345 | // Remove item from RecyclerView
346 | getView().notifyItemRemoved(layoutPos);
347 | getView().showToast(makeToast("Note deleted."));
348 | } else {
349 | // Inform about error
350 | getView().showToast(makeToast("Error deleting note["+note.getId()+"]"));
351 | }
352 | } catch (NullPointerException e) {
353 | e.printStackTrace();
354 | }
355 |
356 | }
357 | }.execute();
358 | }
359 |
360 |
361 | }
362 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tinmegali/tutsmvp_sample/main/activity/view/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.tinmegali.tutsmvp_sample.main.activity.view;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.support.design.widget.FloatingActionButton;
6 | import android.support.v7.app.AlertDialog;
7 | import android.support.v7.app.AppCompatActivity;
8 | import android.support.v7.widget.DefaultItemAnimator;
9 | import android.support.v7.widget.LinearLayoutManager;
10 | import android.support.v7.widget.RecyclerView;
11 | import android.support.v7.widget.Toolbar;
12 | import android.view.View;
13 | import android.view.Menu;
14 | import android.view.MenuItem;
15 | import android.view.ViewGroup;
16 | import android.widget.EditText;
17 | import android.widget.ProgressBar;
18 | import android.widget.Toast;
19 |
20 | import com.tinmegali.tutsmvp_sample.R;
21 | import com.tinmegali.tutsmvp_sample.common.StateMaintainer;
22 | import com.tinmegali.tutsmvp_sample.main.activity.MVP_Main;
23 | import com.tinmegali.tutsmvp_sample.main.activity.model.MainModel;
24 | import com.tinmegali.tutsmvp_sample.main.activity.presenter.MainPresenter;
25 | import com.tinmegali.tutsmvp_sample.main.activity.view.recycler.NotesViewHolder;
26 |
27 | public class MainActivity
28 | extends AppCompatActivity
29 | implements View.OnClickListener, MVP_Main.RequiredViewOps
30 | {
31 |
32 | private EditText mTextNewNote;
33 | private ListNotes mListAdapter;
34 | private ProgressBar mProgress;
35 |
36 | private MVP_Main.ProvidedPresenterOps mPresenter;
37 |
38 | // Responsible to maintain the object's integrity
39 | // during configurations change
40 | private final StateMaintainer mStateMaintainer =
41 | new StateMaintainer( getFragmentManager(), MainActivity.class.getName());
42 |
43 | @Override
44 | protected void onCreate(Bundle savedInstanceState) {
45 | super.onCreate(savedInstanceState);
46 | setContentView(R.layout.activity_main);
47 |
48 | setupViews();
49 | setupMVP();
50 | }
51 |
52 | @Override
53 | protected void onDestroy() {
54 | super.onDestroy();
55 | mPresenter.onDestroy(isChangingConfigurations());
56 | }
57 |
58 | /**
59 | * Setup the Views
60 | */
61 | private void setupViews(){
62 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
63 | setSupportActionBar(toolbar);
64 | FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
65 | fab.setOnClickListener(this);
66 |
67 | mTextNewNote = (EditText) findViewById(R.id.edit_note);
68 | mListAdapter = new ListNotes();
69 | mProgress = (ProgressBar) findViewById(R.id.progressbar);
70 |
71 | RecyclerView mList = (RecyclerView) findViewById(R.id.list_notes);
72 | LinearLayoutManager linearLayoutManager = new LinearLayoutManager( this );
73 | linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
74 |
75 | mList.setLayoutManager(linearLayoutManager);
76 | mList.setAdapter(mListAdapter);
77 | mList.setItemAnimator(new DefaultItemAnimator());
78 | }
79 |
80 | /**
81 | * Setup Model View Presenter pattern.
82 | * Use a {@link StateMaintainer} to maintain the
83 | * Presenter and Model instances between configuration changes.
84 | * Could be done differently,
85 | * using a dependency injection for example.
86 | */
87 | private void setupMVP() {
88 | // Check if StateMaintainer has been created
89 | if (mStateMaintainer.firstTimeIn()) {
90 | // Create the Presenter
91 | MainPresenter presenter = new MainPresenter(this);
92 | // Create the Model
93 | MainModel model = new MainModel(presenter);
94 | // Set Presenter model
95 | presenter.setModel(model);
96 | // Add Presenter and Model to StateMaintainer
97 | mStateMaintainer.put(presenter);
98 | mStateMaintainer.put(model);
99 |
100 | // Set the Presenter as a interface
101 | // To limit the communication with it
102 | mPresenter = presenter;
103 |
104 | }
105 | // get the Presenter from StateMaintainer
106 | else {
107 | // Get the Presenter
108 | mPresenter = mStateMaintainer.get(MainPresenter.class.getName());
109 | // Updated the View in Presenter
110 | mPresenter.setView(this);
111 | }
112 | }
113 |
114 |
115 | @Override
116 | public void onClick(View v) {
117 | switch (v.getId()) {
118 | case R.id.fab:{
119 | // Add new note
120 | mPresenter.clickNewNote(mTextNewNote);
121 | }
122 | }
123 | }
124 |
125 | @Override
126 | public Context getActivityContext() {
127 | return this;
128 | }
129 |
130 | @Override
131 | public Context getAppContext() {
132 | return getApplicationContext();
133 | }
134 |
135 | @Override
136 | public void showToast(Toast toast) {
137 | toast.show();
138 | }
139 |
140 | @Override
141 | public void clearEditText() {
142 | mTextNewNote.setText("");
143 | }
144 |
145 | @Override
146 | public void showProgress() {
147 | mProgress.setVisibility(View.VISIBLE);;
148 | }
149 |
150 | @Override
151 | public void hideProgress() {
152 | mProgress.setVisibility(View.GONE);;
153 | }
154 |
155 | @Override
156 | public void showAlert(AlertDialog dialog) {
157 | dialog.show();
158 | }
159 |
160 | @Override
161 | public void notifyItemRemoved(int position) {
162 | mListAdapter.notifyItemRemoved(position);
163 | }
164 |
165 | @Override
166 | public void notifyItemInserted(int adapterPos) {
167 | mListAdapter.notifyItemInserted(adapterPos);
168 | }
169 |
170 | @Override
171 | public void notifyItemRangeChanged(int positionStart, int itemCount){
172 | mListAdapter.notifyItemRangeChanged(positionStart, itemCount);
173 | }
174 |
175 | @Override
176 | public void notifyDataSetChanged() {
177 | mListAdapter.notifyDataSetChanged();
178 | }
179 |
180 | private class ListNotes extends RecyclerView.Adapter {
181 |
182 |
183 | @Override
184 | public int getItemCount() {
185 | return mPresenter.getNotesCount();
186 | }
187 |
188 | @Override
189 | public NotesViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
190 | return mPresenter.createViewHolder(parent, viewType);
191 | }
192 |
193 | @Override
194 | public void onBindViewHolder(NotesViewHolder holder, int position) {
195 | mPresenter.bindViewHolder(holder, position);
196 | }
197 |
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/app/src/main/java/com/tinmegali/tutsmvp_sample/main/activity/view/recycler/NotesViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.tinmegali.tutsmvp_sample.main.activity.view.recycler;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 | import android.view.View;
5 | import android.widget.ImageButton;
6 | import android.widget.LinearLayout;
7 | import android.widget.RelativeLayout;
8 | import android.widget.TextView;
9 |
10 | import com.tinmegali.tutsmvp_sample.R;
11 |
12 | /**
13 | * ---------------------------------------------------
14 | * Created by Tin Megali on 18/03/16.
15 | * Project: tuts+mvp_sample
16 | * ---------------------------------------------------
17 | * tinmegali.com
18 | * tinmegali.com
13 | *
2 |
9 |
10 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
20 |
21 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/holder_notes.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
23 |
24 |
29 |
30 |
31 |
32 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tutsplus/Android-ModelViewPresenter-Example/74dbc35cef799610cf64153247a75d6ad3a2d630/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tutsplus/Android-ModelViewPresenter-Example/74dbc35cef799610cf64153247a75d6ad3a2d630/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tutsplus/Android-ModelViewPresenter-Example/74dbc35cef799610cf64153247a75d6ad3a2d630/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tutsplus/Android-ModelViewPresenter-Example/74dbc35cef799610cf64153247a75d6ad3a2d630/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tutsplus/Android-ModelViewPresenter-Example/74dbc35cef799610cf64153247a75d6ad3a2d630/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 | >
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #318bff
4 | #30649f
5 | #d72629
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 16dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | tuts+mvp_sample
3 | Settings
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/test/java/com/tinmegali/tutsmvp_sample/DBTest.java:
--------------------------------------------------------------------------------
1 | package com.tinmegali.tutsmvp_sample;
2 |
3 | import android.content.Context;
4 |
5 | import com.tinmegali.tutsmvp_sample.data.DAO;
6 | import com.tinmegali.tutsmvp_sample.models.Note;
7 |
8 | import org.junit.Before;
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 | import org.robolectric.RobolectricGradleTestRunner;
12 | import org.robolectric.RuntimeEnvironment;
13 | import org.robolectric.annotation.Config;
14 |
15 | import java.util.ArrayList;
16 |
17 | import static org.junit.Assert.*;
18 |
19 | /**
20 | * ---------------------------------------------------
21 | * Created by Tin Megali on 18/03/16.
22 | * Project: tuts+mvp_sample
23 | * ---------------------------------------------------
24 | * tinmegali.com
25 | * noteTexts = new ArrayList<>();
61 | noteTexts.add( "note1" );
62 | noteTexts.add( "note2" );
63 | noteTexts.add("note3");
64 |
65 | for( int i=0; i notes = dao.getAllNotes();
71 | assertNotNull( notes );
72 | assertEquals(notes.size(), noteTexts.size());
73 | }
74 |
75 | @Test
76 | public void deleteNote() {
77 | Note note = getNote("deleteNote");
78 | Note noteInserted = dao.insertNote(note);
79 | assertNotNull(noteInserted);
80 |
81 | Note noteFetched = dao.getNote(noteInserted.getId());
82 | assertNotNull(noteFetched);
83 | assertEquals(noteFetched.getText(), note.getText());
84 |
85 | long delResult = dao.deleteNote( noteInserted );
86 | assertEquals(1, delResult);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/app/src/test/java/com/tinmegali/tutsmvp_sample/MainModelTest.java:
--------------------------------------------------------------------------------
1 | package com.tinmegali.tutsmvp_sample;
2 |
3 | import android.content.Context;
4 |
5 | import com.tinmegali.tutsmvp_sample.data.DAO;
6 | import com.tinmegali.tutsmvp_sample.main.activity.model.MainModel;
7 | import com.tinmegali.tutsmvp_sample.main.activity.presenter.MainPresenter;
8 | import com.tinmegali.tutsmvp_sample.models.Note;
9 |
10 | import org.junit.Before;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 | import org.mockito.Mockito;
14 | import org.robolectric.RobolectricGradleTestRunner;
15 | import org.robolectric.RuntimeEnvironment;
16 | import org.robolectric.annotation.Config;
17 |
18 | import java.util.ArrayList;
19 |
20 | import static org.mockito.Mockito.*;
21 | import static org.junit.Assert.*;
22 |
23 | /**
24 | * ---------------------------------------------------
25 | * Created by Tin Megali on 18/03/16.
26 | * Project: tuts+mvp_sample
27 | * ---------------------------------------------------
28 | * tinmegali.com
29 | * ();
47 |
48 | reset(mockPresenter);
49 | }
50 |
51 |
52 | private Note createNote(String text) {
53 | Note note = new Note();
54 | note.setText(text);
55 | note.setDate("some date");
56 | return note;
57 | }
58 |
59 | @Test
60 | public void loadData(){
61 |
62 | int notesSize = 10;
63 | for (int i =0; i -1);
76 | }
77 |
78 | @Test
79 | public void deleteNote() {
80 | Note note = createNote("testNote");
81 | Note insertedNote = mDAO.insertNote(note);
82 | mModel.mNotes = new ArrayList<>();
83 | mModel.mNotes.add(insertedNote);
84 |
85 | assertTrue(mModel.deleteNote(insertedNote, 0));
86 |
87 | Note fakeNote = createNote("fakeNote");
88 | assertFalse(mModel.deleteNote(fakeNote, 0));
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/app/src/test/java/com/tinmegali/tutsmvp_sample/MainPresenterTest.java:
--------------------------------------------------------------------------------
1 | package com.tinmegali.tutsmvp_sample;
2 |
3 | import android.widget.EditText;
4 | import android.widget.Toast;
5 |
6 | import com.tinmegali.tutsmvp_sample.main.activity.MVP_Main;
7 | import com.tinmegali.tutsmvp_sample.main.activity.model.MainModel;
8 | import com.tinmegali.tutsmvp_sample.main.activity.presenter.MainPresenter;
9 | import com.tinmegali.tutsmvp_sample.models.Note;
10 |
11 | import org.junit.Before;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 | import org.mockito.Mockito;
15 | import org.robolectric.RobolectricGradleTestRunner;
16 | import org.robolectric.annotation.Config;
17 |
18 | import static org.mockito.Mockito.*;
19 |
20 | /**
21 | * ---------------------------------------------------
22 | * Created by Tin Megali on 18/03/16.
23 | * Project: tuts+mvp_sample
24 | * ---------------------------------------------------
25 | * tinmegali.com
26 | *