Today i'm gonna show, how i apply the MVC pattern while developing android applications. I found it so usefull and it separates the concerns in such a nice way, that i think it could be also helpful for others out there.
Some people don't know why we should do that or what is the advantage of it exactly. So before you ask me that ;) here is the explanation of Martin Follower, which a share:
Definition of MVC
"At the heart of MVC is what I call Separated Presentation. The idea behind Separated Presentation is to make a clear division between domain objects that model our perception of the real world, and presentation objects that are the GUI elements we see on the screen. Domain objects should be completely self contained and work without reference to the presentation, they should also be able to support multiple presentations, possibly simultaneously. The fully article can be read here."And that is exactly what i'm trying to show bellow. A way to decouple View from Model. So in this approach bellow, my View is designed entirely without any logic and don't care about the Model. The model istself contains alls business logics but don't know the View. The Controller is the mediator and is the only one who knows both Model and View. But it does not implement UI or Business Logic. Instead of that, it delegates (mediates) to its delegatees.
Class diagram
To show how it could looks like, i've created a simple calculator project. Lets take a look at the UML and Sequence-diagram first:Sequence diagram
Und here the sequence diagram:In the sequence diagram above, the activity representes the HI (Human Interface - Hardware), the View represents the GUI (Grafical User Interface), the Model is responsible for all business logics, the Controller is the mediator and the Calculator, Reseter, DbUpdater and ViewUpdater are the Delegatees from the Controller. They may have access to Model, to View or to both of them.
Creating the project
Create a simple blank project and define the structure like this:Here you can already see the first advantage of it. We have separeted packages and a clear structure of our architecture already. It makes a lot easier to explain things especially if you are working with distributed teams. For each activity you will have a group of 3 elementes. (MVC)
Creating the Model
The model contains just business logics. In our case a simple calculation. Which can be tested separeted thru junit tests for example. It is not allowed to have any reference to our View. Our example looks like this:// MODEL'S RESPONSABILITY: // // ACCESS DB USING DAO's AND COMPUTE BUSINESS LOGICS ONLY. IT COMMUNICATES ONLY // WITH THE CONTROLLER. // // IMPORTANT: IT DOESN'T CARE ABOUT VIEW AND IT DOESN'T KNOW NOTHING ABOUT THE VIEW public class CalculatorModel implements Serializable { private static final long serialVersionUID = 1L; // YOU MAY STORE THING IN THE DB AND QUERY FOR INFORMATION // IN THIS SIMPLE EXAMPLE WE JUST HAVE A SIMPLE CALCULATOR MODEL private int lastResult; private int actualResult; public void add2Numbers(int firstNumber, int secondNumber) { this.lastResult = firstNumber + secondNumber; this.actualResult = this.lastResult; } public int getActualResult() { return actualResult; } public int getLastResult() { return lastResult; } public void resetActualResult() { this.actualResult = 0; } }
Creating the totally dummy View
The view is totally dumm and has no logic inside of it. It contains just the UI objects that will be shown to the user later. Something like this:define a layout like this for it:
< relativelayout android:layout_height="match_parent" android:layout_width="match_parent" android:paddingbottom="@dimen/activity_vertical_margin" android:paddingleft="@dimen/activity_horizontal_margin" android:paddingright="@dimen/activity_horizontal_margin" android:paddingtop="@dimen/activity_vertical_margin" tools:context=".CalculatorActivity" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> < textview android:id="@+id/textView1" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="Calulator"> < edittext android:ems="10" android:id="@+id/firstNumberField" android:layout_alignleft="@+id/textView1" android:layout_alignparentright="true" android:layout_below="@+id/textView1" android:layout_height="wrap_content" android:layout_margintop="16dp" android:layout_width="wrap_content"/ > < edittext android:ems="10" android:id="@+id/secondNumberField" android:layout_alignleft="@+id/firstNumberField" android:layout_alignright="@+id/firstNumberField" android:layout_below="@+id/firstNumberField" android:layout_height="wrap_content" android:layout_margintop="18dp" android:layout_width="wrap_content" / > < button android:id="@+id/calculateButton" android:layout_alignleft="@+id/secondNumberField" android:layout_below="@+id/secondNumberField" android:layout_height="wrap_content" android:layout_margintop="24dp" android:layout_width="wrap_content" android:text="calculate"/ > < textview android:id="@+id/calculationResult" android:layout_alignleft="@+id/calculateButton" android:layout_alignright="@+id/calculateButton" android:layout_below="@+id/calculateButton" android:layout_height="wrap_content" android:layout_margintop="28dp" android:layout_width="wrap_content" android:text="Result" android:textappearance="?android:attr/textAppearanceLarge"/ > < textview android:id="@+id/lastCalculationResult" android:layout_alignbottom="@+id/textView1" android:layout_alignright="@+id/firstNumberField" android:layout_height="wrap_content" android:layout_marginleft="37dp" android:layout_torightof="@+id/textView1" android:layout_width="wrap_content" android:text="Last Calculation Result"/ > < /textview>< /textview>< /button>< button android:id="@+id/resetButton" android:layout_alignbottom="@+id/calculateButton" android:layout_alignright="@+id/secondNumberField" android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="Reset"/ > < /relativelayout>
Then create a class in the view package like this:
// VIEW'S RESPONSABILITY: // // INSTANTIATE ALL COMPONENTS AND BIND IT TO THE CONTROLLER. IT COMMUNICATES ONLY WITH // THE CONTROLLER. // // IMPORTANT: IT DOESN'T CARE ABOUT MODEL AND IT DOESN'T KNOW NOTHING ABOUT THE MODEL public class CalculatorView { private EditText firstNumberField; private EditText secondNumberField; private TextView lastCalculationResult; private TextView calculationResult; private Button calculateButton; private Button resetButton; protected Activity context; public CalculatorView(Activity context) { this.context = context; defineViewOrientation(); firstNumberField = (EditText) context.findViewById(R.id.firstNumberField); secondNumberField = (EditText) context.findViewById(R.id.secondNumberField); calculationResult = (TextView) context.findViewById(R.id.calculationResult); lastCalculationResult = (TextView) context.findViewById(R.id.lastCalculationResult); calculateButton = (Button) context.findViewById(R.id.calculateButton); resetButton = (Button) context.findViewById(R.id.resetButton); } private void defineViewOrientation(){ context.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } public void bind(CalculatorController listener) { calculateButton.setOnClickListener(listener); resetButton.setOnClickListener(listener); } public EditText getFirstNumberField() { return firstNumberField; } public EditText getSecondNumberField() { return secondNumberField; } public TextView getLastCalculationResult() { return lastCalculationResult; } public TextView getCalculationResult() { return calculationResult; } }
Creating the Controller
To complete the MVC, define a controller which knows about both model and view.// CONTROLLER'S RESPONSABILITY: // // MEDIATE USER'S ACTIONS BETWEEN MODEL AND VIEW. INTERACTS AS A DELEGATOR. // // THIS IS THE CENTRAL PLACE TO LOOK FOR BEHAVIORS. EVERYTHING HAPPENS HERE... public class CalculatorController extends Observable implements OnClickListener { private CalculatorModel model; private CalculatorView view; public CalculatorController(CalculatorModel model, CalculatorView view) { super(); setModel(model); setView(view); bindControllerOnView(); addObservers(); } public void addObservers() { addObserver(new Calculator()); addObserver(new Reseter()); } private class Calculator implements Observer { @Override public void update(Observable observable, Object object) { if (R.id.calculateButton == ((View) object).getId()) { String first = getView().getFirstNumberField().getText().toString(); String second = getView().getSecondNumberField().getText().toString(); int firstNumber = Integer.valueOf(first); int secondNumber = Integer.valueOf(second); getModel().add2Numbers(firstNumber, secondNumber); int result = getModel().getActualResult(); getView().getCalculationResult().setText(String.valueOf(result)); getView().getLastCalculationResult().setText("Last Result: " + result); } } } private class Reseter implements Observer { @Override public void update(Observable observable, Object object) { if (R.id.resetButton == ((View) object).getId()) { getView().getCalculationResult().setText(String.valueOf(0)); getView().getFirstNumberField().setText(""); getView().getSecondNumberField().setText(""); } } } @Override public void onClick(View view) { this.setChanged(); this.notifyObservers(view); } public void setView(CalculatorView view) {this.view = view;} public CalculatorView getView() {return view;} public void setModel(CalculatorModel model) {this.model = model;} public CalculatorModel getModel() {return model;} public void bindControllerOnView(){getView().bind(this);} public void updateView() {/*Auto-generated method stub*/} public void updateDb() {/*Auto-generated method stub*/} }
Using it in the CalculatorActivity
Finally call it in the activity. Take your time to understand it and see how small and understandable the class are. This approach also leads to SRP (single responsibility principle) which is desired and much better then having all havy weight code in one single class.The Usage
The final code looks like this:// WHY WE DO THAT? BECAUSE CLEAN CODE MATTERS!!! ;) // // IMAGINE THIS CLASS AS THE CLIENT (THE MAIN CLASS) // EACH ACTIVITY IS REPRESENTED BY A GROUP OF MVC'S // // IMPORTANT: USES THE CONTROLLER TO DELEGATE EVERYTHING. // IT DOESN'T CARE ABOUT THE IMPLEMENTATION. // public class CalculatorActivity extends Activity { protected CalculatorController controller; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // MVC - APPLIED IN THE PRACTISE controller = new CalculatorController(new CalculatorModel(), new CalculatorView(this)); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // THIS IS JUST AN EXAMPLE, BUT YOU MAY DELEGATE THINGS ON: // onPause(), onResume(), onWhatEverYouNeed() ... // controller.updateView(); controller.updateDb(); } }
Conclusions
I found this way so nice and so clear, that i'm using it in my projects with a lot of possitive feedbacks. Maintenance has become a lot more easier and adding new features has also become a lot more clear and faster. Of course thats not the final approach. I did it that way here to make it simple and to show the idea and advantages behind it. In the final approach i have a defined a abstract model like this: public abstract class MvcModel implements Serializable..., an abstract generic Controller public abstract class MvcController😱👇 PROMOTIONAL DISCOUNT: BOOKS AND IPODS PRO 😱👇
Be sure to read, it will change your life!
Show your work by Austin Kleon: https://amzn.to/34NVmwx
This book is a must read - it will put you in another level! (Expert)
Agile Software Development, Principles, Patterns, and Practices: https://amzn.to/30WQSm2
Write cleaner code and stand out!
Clean Code - A Handbook of Agile Software Craftsmanship: https://amzn.to/33RvaSv
This book is very practical, straightforward and to the point! Worth every penny!
Kotlin for Android App Development (Developer's Library): https://amzn.to/33VZ6gp
Needless to say, these are top right?
Apple AirPods Pro: https://amzn.to/2GOICxy
😱👆 PROMOTIONAL DISCOUNT: BOOKS AND IPODS PRO 😱👆