What is a View?
In Android, Activities are going to be our architectural views. Having the Activity serve the role of a view isn’t too intuitive. Because Android creates the Activity for us and manages its lifecycle, it’s difficult to conceptualize the role an Activity should do. Heck, it even comes with tons of hooks like:
(UPDATE: These next 2 articles on Views and Controllers are a “bit off.” The idea comes from of a blog post here (which is a great blog and has tons of good Android stuff in it) to which I elaborated on for this series. Since the writing of this post, I have switched the roles discussed. It works worlds better and I’ve never looked back. The activity now functions as the controller and views are a composed subclass of one of the layout files like RelativeLayout or LinearLayout. This post still has some good information in it about controller/views and all the oddities I discuss are resolved by switching the roles. An example may be seen here in Part 10.)
- boolean dispatchKeyEvent(KeyEvent event); and
- boolean onOptionsItemSelected(MenuItem item); and
- void onCreateContextMenu(….); and
- so on….
These are all very controller-ish methods. Among the weirdest is dispatchKeyEvent() because it expects a boolean returned representing if the event was handled.
Observant guy says: “But wait a minute here…I thought views aren’t supposed to interpret user input and now our view has to and return back a boolean representing whether or not it handled the event!”
Yup, Activities are a little odd; they do so much stuff in the Android framework. Between Android managing its lifecycle, being the entry point to the application, inflating other views/widgets and all the odd hooks, creating a strict MVC in Android is not very intuitive. But have no fear, let’s show some code and talk about how to make Activities a view and only do viewy things. Those viewy things consist of 3 main concepts: 1) binding to the model, 2) sending messages to the controller, and 3) handling messages from the controller.
Role 1, Data Binding:
Let’s look at TapActivity and let’s bind some data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | package com.musselwhizzle.tapcounter.activities; public class TapActivity extends Activity implements OnChangeListener { private static final String TAG = TapActivity.class.getSimpleName(); private CounterVo counter; private EditText label; private TextView count; private Button plusBtn; private Button minusBtn; private CompoundButton lockedBtn; private Dialog saveDialog; public static final String EXTRA_TAP_ID = "tapId"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); counter = new CounterVo(); counter.addListener(this); label = (EditText)findViewById(R.id.label); // truncated but just referencing more views/widgets. } @Override public void onChange(CounterVo counter) { runOnUiThread(new Runnable() { @Override public void run() { updateView(); } }); } private void updateView() { if (!label.getText().toString().equals(counter.getLabel())) label.setText(counter.getLabel()); label.setEnabled(!counter.isLocked()); count.setText(Integer.toString(counter.getCount())); lockedBtn.setChecked(counter.isLocked()); } } |
So in our onCreate() we instantiate our model and set the activity as an observer as we talked about in part 3.
22 23 | counter = new CounterVo(); counter.addListener(this); |
Now anytime the model changes, the onChange method gets called. Since a change has occurred we need to update our widgets/sub-views. Remember, widgets must be modified on the UI Thread. Since we don’t know what thread called onChange, we need to switch over to the UI thread before making an modification which is done by calling the runOnUiThread. The updateView() method is the one that is responsible for syncing all of our widgets to our model’s data. Boom! We got data binding. Our Activity is registered as an observer and whenever our model is updated we know our UI is going to stay right in-sync.
Okay, so that was section 1 of the View. Grab a drink or take a little break if you need it. We’re on to section 2 of sending messages to the controller.
Role 2, Sending Messages:
So this is where we address those “odd issues” of Activties that were mentioned earlier. It’s actually quite simple: delegation. We delegate to the controller our user input and that even goes for the dispatchKeyEvent() method we mentioned early. Let’s show some code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | package com.musselwhizzle.tapcounter.activities; public class TapActivity extends Activity implements OnChangeListener { private static final String TAG = TapActivity.class.getSimpleName(); private TapController controller; private CounterVo counter; public static final String EXTRA_TAP_ID = "tapId"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); counter = new CounterVo(); counter.addListener(this); controller = new TapController(counter); // .... finding widgets ... int tapId = getIntent().getIntExtra(EXTRA_TAP_ID, -1); controller.handleMessage(TapController.MESSAGE_POPULATE_MODEL_BY_ID, tapId); plusBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { controller.handleMessage(TapController.MESSAGE_INCREMENT_COUNT); } }); minusBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { controller.handleMessage(TapController.MESSAGE_DECREMENT_COUNT); } }); } @Override public boolean dispatchKeyEvent(KeyEvent event) { boolean handled = controller.handleMessage(TapController.MESSAGE_KEY_EVENT, event); if (!handled) { return super.dispatchKeyEvent(event); } return handled; } } |
Besides data binding, views send messages to its controller. How exactly we do that will be covered in the controller’s section. But let’s take a little peek, specifically at two methods. First, look at the dispatchKeyEvent method that we were struggling with earlier. When this method is called, we simply delegate the logic to the controller by sending a message asking it to handle the KeyEvent for us. Our controller returns a boolean indicating whether or not the message was handled. If the controller didn’t handle the KeyEvent the method calls its super. Simple solution.
Let’s look at handling direct user input from a button. Let’s say the user clicks on the plusBtn button. Once again, we send a message to the controller asking it to handle this event for us like so:
controller.handleMessage(TapController.MESSAGE_INCREMENT_COUNT); |
Wait a minute….but how does this update our widgets? We’re only sending a message to the controller. We don’t update the screen or anything. This doesn’t make sense. ARRRGGGHHHH!
Okay, calm down. Remember data binding? When the message is sent to the controller, the controller updates the model. Because the view is registered as an observer of the model, the onChange() method is called. From there we update our widgets. Makes sense, doesn’t it?
Role 3: Handling Messages
The final thing views can do is handle messages from the controller. It’s very similar to how our controller handles messages. If you want to see a example check out TapListActivity. It handles messages from the controller.
Example Files:
If you haven’t done so yet, grab the sources of our example here.
12 responses so far ↓
1 ben // Dec 29, 2011 at 2:51 pm
hey josh, great post! did you have a chance to rework your tutorial using activity as a pure controller? thanks again!
2 musselwhizzle // Dec 29, 2011 at 11:37 pm
Hi Ben, no I haven’t. I used the concept in the current project I’m working on. It really isn’t much different than what’s shown here. The most important part isn’t necessary if the Activity is a View or if it’s a Controller, but let it do one or the other and only that. In the coming weeks, I’ll try to make an updated Activity-Controller to further clarify. Thanks for reading and commenting. Hope you enjoyed it.
3 Brandon // Dec 31, 2011 at 1:45 am
Just reading through this, for the first time, so I admit I may not have my head wrapped around it all properly. But, it seems like activities function more like a presenter in the MVP pattern. Would the project make more sense as an MVP instead of MVC?
4 musselwhizzle // Dec 31, 2011 at 4:51 am
@Brandon, Some people use MVP and while others use MVC. MVP is a variant of MVC. From what I’ve seen so far,
* MindTheRobot had the Activity as a View.
* Random people had the Activity as a anti-pattern View-Controller.
* Others had the Activity as a Presenter.
* Me, based on the all controller-ish hooks of the Activity, I’m suggesting it should function as a Controller.
I favor MVC over MVP and think the architecture will hold up better when I start introducing Front Controllers into my schema.
5 ben // Jan 4, 2012 at 7:44 pm
hey josh, with the way you’re using OnChangeListener, how can you tell which attribute of a model changed? You won’t be able to perform specific actions depending what is changed. If you had a bunch of attributes in your model class that maps to a view each, you have to update all the views every time any part of the model changed?
6 musselwhizzle // Jan 4, 2012 at 7:59 pm
@ben, you’re absolutely correct. It’s a lazy approach. I really do use it when my projects & models are simple and when the view doesn’t require complex action to update itself. The more sophisticated approach, and I use this when my project is more complex, is to use an OnPropertyChangeListener which looks like onPropertyChange(String propertyName, Object newValue) and my Views check to see if they property they care about has changed. Perhaps I should update “The Model” blog post to reflect your concerns? It similar to this article: http://www.oracle.com/technetwork/articles/javase/mvc-136693.html#5
7 ben // Jan 5, 2012 at 12:28 pm
i see, yeah that makes more sense … the oracle article was very useful indeed! i tried using the MVC model according to the iOS standards where the view doesn’t talk to the model. I found it pretty clean where all models and views are completely independent of each other, but there are so much message passing going on. In android, I used the activity as a go to point for the controller to update UI assets. Basically the activity registers itself to the controller as a listener, whenever the controller requires a UI update it broadcasts a message to the activity which will then spawn a UI thread to send a message back to the controller to execute the UI update. Is that the way you’re doing it too in your other project where you made the activity your controller? the only difference here is that my activity creates a controller and then lets it do the rest of the work and just acts as a UI “jobs” listener. What are your thoughts?
8 ben // Jan 5, 2012 at 12:39 pm
actually now that i think about it, by doing something like a generic onPropertyChange, you’re leaving the controller to deal like the logic at run time. Instead if you had specific listeners like for example, onWidthChanged and onHeightChanged, all the complexity is figured out at compile time. Isn’t this a better approach for performance? The only downfall to this is that you need to write a different interface for each model.
9 Samus Arin // May 9, 2012 at 11:37 am
Great tutorial, thanks, its really helping…
Something is up with source zip file. I get all these prompts about encryption and file overwrites.
You may want to rezip or something, because it crashed explorer (three times, on windows 7!) trying to unzip. It was a nightmare.
Good tutorial though.
10 Jeannius81 // Jun 7, 2012 at 12:48 am
Great tutorial. I really learn alot about MVC for android on this. Could you please elaborate about having the activity as a Controller instead of a View (especially, how would you go about composing the view). Thanks man
11 Deepak // Jul 7, 2012 at 11:05 am
Eagerly waiting to see your updates on “Activities should function strictly as Controllers”. This is undoubtedly the best series of articles I have ever come across… thanks a lot
12 Android Architecture: Part 10, The Activity Revisited - Android Developer // Jul 9, 2012 at 2:21 pm
[...] in part 4 of the series, I mentioned that the Activity naturally functions like a controller and not a View. If you examine [...]
Leave a Comment