Android, Flash, Flex, and PHP

Portfolio Site and Blog

Android, Flash, Flex, and PHP header image 2

Android Architecture: Part 6, Putting it Together

November 25th, 2011 · No Comments · Android, OOP

So far we’ve discussed Models, Views, and Controllers. And now let’s discuss how we stick them together to make 1 cohesive unit of the MVC paradigm.

The Activity is our entry point for each new view of our application and it’s in the onCreate() method that we will need to instantiate our model and controller. In a typical MVC the view does not create these but, eh, what can you do. Once the model is created, we need to register the activity as a listener. And additionally, if you want to receive messages from the controller, you can register it as well. Let’s take a quick look at what that looks like.

Activity:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
 
    counter = new CounterVo();
    counter.addListener(this);
    controller = new TapController(counter);
    controller.addOutboxHandler(new Handler(this));
    // ... other set up code like referencing widgets/views ...
}

Here the view is registered to the model and controller as an observer. Let’s check out the callback for the model.

@Override
public void onChange(CounterVo counter) {
    runOnUiThread(new Runnable() {
        @Override
	public void run() {
	    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());
	}
    });
}

Once the model notifies the view of a change, we just update the UI widgets on the UI thread. That’s your data binding: easy peasy. Now let’s check out handling messages sent from the controller.

@Override
public boolean handleMessage(Message msg) {
    switch(msg.what) {
        case TapListController.MESSAGE_MODEL_UPDATED:
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    adapter.notifyDataSetChanged();
                }
             });
             return true;
     }
     return false;
}

This should very much look like how the controller handles messages sent from the view, as discussed earlier. Here the view selectively listens to messages of MESSAGE_MODEL_UPDATED and notifies the list adapter that its data has been updated. If a message other than MESSAGE_MODEL_UPDATED is sent, the view ignores it. It’s worth pointing out that not all messages need to be handled. You may choose to send more messages than needed and you may choose to ignore messages that aren’t of interest.

What does the rest of an Activity look like? I get there’s data binding to a model and handling messages from a controller, but what else are you putting in there? Should I thread here and create background services?

Remember the purpose of a view and that we are striving to keep our Activity as “dumb” as possible and that means it should just do what its told. While activities unfortunately must do more, keep their logic as minimal as possible. What do I mean but that? Well, it would be overkill to delegate all the activities lifecycle hooks to a controller. It’s okay to put some code in onRestart, onResume, onDestroy. But when there is heavy lifting to be done, delegate to the controller.

Controller:
The controller gets a reference to the model and communicates to the view through messages. It’s the brain in the MVC. We’ve already talked about how the controller takes messages, so let’s discuss for a moment what it does with that messages. The answer: whatever and however it wants! How the messages get handed should be completely transparent; we want a well encapsulated controller. But let’s discuss some of the techniques of what it does. In our example, when a message of TapListController.MESSAGE_INCREMENT_COUNTER is received, the controller updates the model’s count by one and persists the model’s state by delegating that task to a DAO (to be discussed later). It then a refreshes the model and sends out a notification of the change. All of this work in done in a separate thread and its implementation of how it’s done is transparent.  Let’s see the code:

package com.musselwhizzle.tapcounter.controllers;
 
import java.util.ArrayList;
 
import android.os.Handler;
import android.os.HandlerThread;
 
import com.musselwhizzle.tapcounter.daos.CounterDao;
import com.musselwhizzle.tapcounter.vos.CounterVo;
 
public class TapListController extends Controller {
	private static final String TAG = TapListController.class.getSimpleName();
	private HandlerThread workerThread;
	private Handler workerHandler;
 
	public static final int MESSAGE_GET_COUNTERS = 1;
	public static final int MESSAGE_MODEL_UPDATED = 2;
	public static final int MESSAGE_DELETE_COUNTER = 3;
	public static final int MESSAGE_INCREMENT_COUNTER = 4;
	public static final int MESSAGE_DECREMENT_COUNTER = 5;
 
	private ArrayList model;
	public ArrayList getModel() {
		return model;
	}
 
	public TapListController(ArrayList model) {
		this.model = model;
		workerThread = new HandlerThread("Worker Thread");
		workerThread.start();
		workerHandler = new Handler(workerThread.getLooper());
	}
 
	@Override
	public void dispose() {
		super.dispose();
		workerThread.getLooper().quit();
	}
 
	@Override
	public boolean handleMessage(int what, Object data) {
		switch(what) {
			case MESSAGE_GET_COUNTERS:
				getCounters();
				return true;
			case MESSAGE_DELETE_COUNTER:
				deleteCounter((Integer)data);
				getCounters();
				return true;
			case MESSAGE_INCREMENT_COUNTER:
				changeCount(1, (CounterVo)data);
				getCounters();
				return true;
			case MESSAGE_DECREMENT_COUNTER:
				changeCount(-1, (CounterVo)data);
				getCounters();
				return true;
		}
		return false;
	}
 
	private void changeCount(final int amount, final CounterVo counter) {
		workerHandler.post(new Runnable() {
			@Override
			public void run() {
				synchronized (counter) {
					counter.setCount(counter.getCount() + amount);
					CounterDao dao = new CounterDao();
					dao.update(counter);
				}
			}
		});
 
	}
 
	private void getCounters() {
		workerHandler.post(new Runnable() {
			@Override
			public void run() {
				CounterDao dao = new CounterDao();
				ArrayList counters = dao.getAll();
				synchronized (model) {
					while(model.size() > 0) {
						model.remove(0);
					}
					for (CounterVo counter : counters) {
						model.add(counter);
					}
					notifyOutboxHandlers(MESSAGE_MODEL_UPDATED, 0, 0, null);
				}
			}
		});
	}
 
	private void deleteCounter(final int itemId) {
		workerHandler.post(new Runnable() {
			@Override
			public void run() {
				CounterDao dao = new CounterDao();
				dao.delete(itemId);
			}
		});
	}
}

Other Notes of Thought:
In this application, each activity/view has its own specific controller and own model. TapActivity has TapController and TapListActivity has TapListController. Sometimes I allow the model to be destroyed when the activity is destroy. Other times, I have an application model that multiple activities bind to that I don’t want destroyed between activities. I achieve this by making my application model a singleton. This will not be something I discuss in this series but I thought it was worth noting. A instance where the use a singleton model could be helpful would be for something like a RSS feed where 1 activity displays summaries and another activity displays the full text. You wouldn’t want to reload the data between activities so it would be worth keeping the model in memory. In any case, whether you destroy your model or not it’s still an MVC. You just have to choose what fits your needs.

Summary:
By now you should have a good understanding how the three parts fit together in an Android application. Take a little time to check out the source code of the application. Each Activity comes with it’s own controller and model. When looking TapController, it may look a little strange at first but all it is doing is delegating the messages. And once again, this delegation is transparent. We’ll talk more in depth about this concept and how it is a use of the State Pattern and why I used it.

Example Files:
If you haven’t done so yet, grab the sources of our example here.

Tags: ···

0 responses so far ↓

  • There are no comments yet...Kick things off by filling out the form below.

Leave a Comment


3 + = twelve