We’ve made it through all the hard stuff. If you can do MVC in Android, the rest of this series is going to easy. Grab a beer and reward yourself and let’s talk about data persistence.
I’m going to assume you already know about sqlite and other persistence features in Android. If not, now is a good time to go check that out. I’m going to suggest a concept known as Data Access Objects or DAOs. The idea is super simple: we pass a model to an instance of a DAO and it saves, updates, or deletes for us. So instead of having tons of sql spaghetti string queries all over the place, it’s all done through the DAO. Seems easy, right? Right. Let’s check out 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 48 49 50 51 52 53 54 55 56 57 58 | package com.musselwhizzle.tapcounter.daos; public class CounterDao { protected static final String TABLE = "Counter"; protected static final String _ID = "_id"; protected static final String LABEL = "label"; protected static final String COUNT = "count"; protected static final String LOCKED = "locked"; public CounterDao() { // } public ArrayList<CounterVo> getAll() { // ... truncated.... returns all CounterVos } public CounterVo get(int id) { SQLiteDatabase db = new DatabaseHelper().getWritableDatabase(); Cursor cursor = db.query(TABLE, null, _ID+"=?", new String[] {Integer.toString(id)}, null, null, null); CounterVo vo = null; if (cursor.moveToFirst()) { vo = new CounterVo(); vo.setId(cursor.getInt(cursor.getColumnIndex(_ID))); vo.setLabel(cursor.getString(cursor.getColumnIndex(LABEL))); vo.setCount(cursor.getInt(cursor.getColumnIndex(COUNT))); vo.setLocked(cursor.getInt(cursor.getColumnIndex(LOCKED)) == 1); } cursor.close(); db.close(); return vo; } public long insert(CounterVo counterVo) { SQLiteDatabase db = new DatabaseHelper().getWritableDatabase(); ContentValues values = new ContentValues(); if (counterVo.getId() > 0) values.put(_ID, counterVo.getId()); values.put(LABEL, counterVo.getLabel()); values.put(COUNT, counterVo.getCount()); values.put(LOCKED, counterVo.isLocked()); long num = db.insert(TABLE, null, values); db.close(); return num; } public int update(CounterVo counterVo) { // ... truncated.... } public void delete(int id) { SQLiteDatabase db = new DatabaseHelper().getWritableDatabase(); db.delete(TABLE, _ID+"=?", new String[]{Integer.toString(id)}); db.close(); } } |
CounterDao is my gateway to the persisted CounterVo objects. They could be stored as sqlite, json, xml, or shared preferences and it doesn’t matter. Anytime I need a to save or fetch a CounterVo I just ask this bad puppy.
What if I need more ways than fetch my data than just a get by id? Don’t I have to write specific sql queries in my views?
First off, no business logic should be in your views. I take it you mean controller. Secondly, just add the methods you need. There are no hard and fast rules of what methods should be in a DAO. Typically, you have saves, fetches, and deletes but do whatever you need to fit the constraints of your application. The concept that is the important part: we are sending our model or value object to be persisted and how it’s done is transparent to the client.
What if in my application I had a CounterVo and let’s say a PersonVo which describes the kinds of people I count. Where would I put the methods to persist PersonVo?
Easy, you’d just make a PersonDao and put your fetch, save, and delete methods in there. Now you have two DOAs: 1 for CounterVos and 1 for PersonVos.
But back on topic, let’s see how we use a DAO. You’re going to love it.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 | private void populateModel(final int id) { if (id < 0) return; workerHandler.post(new Runnable() { @Override public void run() { synchronized (model) { CounterDao dao = new CounterDao(); CounterVo vo = dao.get(id); if (vo == null) vo = new CounterVo(); model.consume(vo); } } }); } |
Above is an except from one of our controller’s states, TapState. The method is called when a request is made from the view to populate the model. The controller shoves this task into a thread and then asks the DAO to get a copy of CounterVo as seen on lines 88 and 89. So easy, right?!?!
Why can’t I just put a save method on my VO object? Doesn’t that make sense?
Yeah, I’ve seen other Android developers doing this (which is why I’m writing this series), but it’s not a good idea. Remember the Single Responsibility Principle we talked about earlier? Values Objects should just hold state. It’s not their job to do more and doing so would violate SRP. There are some other good reasons as well, but 1 good reason is enough for me. (Hint: the others involve factories and polymorphism. Consider saving as JSON or sqlite based upon some factor, what could you do? Perhaps CounterJsonDao and CounterSqlDao with a common interface…)
One final note, I’ve keep the table names of my database in this DAO as constants. You should avoid working with strings when possible as it’s more susceptible to typos. Also, the constants are protected. In Java, classes in the same package have access to protected properties and methods much like subclasses do. They are protected because DatabaseHelper references them to create the actual database.
So Far:
So now in our application we have something like this:
- the view asks controller for new data,
- the controller delegates the fetching of the data to the DAO
- the controller adds the data to the model
- the model sends notification that its state has changed
- the view updates to reflect the new state
Pretty cool separation of parts, eh?
Example Files:
If you haven’t done so yet, grab the sources of our example here.
2 responses so far ↓
1 Chris // Dec 29, 2011 at 11:04 pm
“the controller delegates the fetching of the data to the DAO”
“the controller adds the data to the model”
That makes sense. Now I need a project to try this all out on
2 musselwhizzle // Dec 29, 2011 at 11:50 pm
Anything that persists data is a great candidate. When you launch your app that uses DAOs, come back and post a link.
Leave a Comment