TBR: TODOs: Factoring out persistence

TODO(rosswang): Rename things now that hierarchy is clear.

Change-Id: I68f6268e9d2aa82fd8df2986d378fa7dca4def56
diff --git a/projects/todos/app/src/main/java/io/v/todos/KeyedData.java b/projects/todos/app/src/main/java/io/v/todos/KeyedData.java
index 20da7ad..7abae0d 100644
--- a/projects/todos/app/src/main/java/io/v/todos/KeyedData.java
+++ b/projects/todos/app/src/main/java/io/v/todos/KeyedData.java
@@ -10,6 +10,7 @@
  *
  * @author alexfandrianto
  */
-public abstract class KeyedData<T> implements Comparable<T> {
-    public abstract String getKey();
+public interface KeyedData<T> extends Comparable<T> {
+    String getKey();
+    void setKey(String key);
 }
diff --git a/projects/todos/app/src/main/java/io/v/todos/MainActivity.java b/projects/todos/app/src/main/java/io/v/todos/MainActivity.java
index 0157f18..56f2f0c 100644
--- a/projects/todos/app/src/main/java/io/v/todos/MainActivity.java
+++ b/projects/todos/app/src/main/java/io/v/todos/MainActivity.java
@@ -11,7 +11,6 @@
 import android.support.v7.app.AlertDialog;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.helper.ItemTouchHelper;
-import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
@@ -19,12 +18,8 @@
 import android.widget.EditText;
 import android.widget.Toolbar;
 
-import com.firebase.client.ChildEventListener;
-import com.firebase.client.DataSnapshot;
-import com.firebase.client.Firebase;
-import com.firebase.client.FirebaseError;
-
-import io.v.todos.persistence.Persistence;
+import io.v.todos.persistence.ListEventListener;
+import io.v.todos.persistence.MainPersistence;
 import io.v.todos.persistence.PersistenceFactory;
 
 /**
@@ -38,27 +33,20 @@
  * @author alexfandrianto
  */
 public class MainActivity extends Activity {
-    static final String FIREBASE_EXAMPLE_URL = "https://vivid-heat-7354.firebaseio.com/";
-    private Persistence mPersistence;
-    private Firebase myFirebaseRef; // TODO(rosswang): migrate
+    private MainPersistence mPersistence;
 
     // Snackoos are the code name for the list of todos.
     // These todos are backed up at the SNACKOOS child of the Firebase URL.
     // We use the snackoosList to track a custom sorted list of the stored values.
     static final String INTENT_SNACKOO_KEY = "snackoo key";
-    static final String INTENT_SNACKOO_VALUE = "snackoo value";
-    static final String SNACKOOS = "snackoos (TodoList)";
     private DataList<TodoList> snackoosList = new DataList<TodoList>();
 
     // This adapter handle mirrors the firebase list values and generates the corresponding todo
     // item View children for a list view.
     private TodoListRecyclerAdapter adapter;
 
-    private ChildEventListener snackoosEventListener;
-
     @Override
     protected void onDestroy() {
-        myFirebaseRef.removeEventListener(snackoosEventListener);
         mPersistence.close();
         super.onDestroy();
     }
@@ -72,13 +60,12 @@
         getActionBar().setTitle(R.string.app_name);
 
         // Set up the todo list adapter
-        final Activity self = this;
         adapter = new TodoListRecyclerAdapter(snackoosList, new View.OnClickListener() {
             @Override
             public void onClick(View view) {
                 String fbKey = (String)view.getTag();
 
-                Intent intent = new Intent(self, TodoListActivity.class);
+                Intent intent = new Intent(MainActivity.this, TodoListActivity.class);
                 intent.putExtra(INTENT_SNACKOO_KEY, fbKey);
                 startActivity(intent);
             }
@@ -92,41 +79,19 @@
             @Override
             public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int direction) {
                 if (direction == ItemTouchHelper.RIGHT) {
-                    Log.d(SNACKOOS, "Gonna mark all tasks as done");
-
-                    // TODO(alexfandrianto): This doesn't do anything yet. Should mark all child Tasks as done.
+                    // TODO(alexfandrianto): This doesn't do anything yet. Should mark all child
+                    // Tasks as done.
                     adapter.notifyDataSetChanged();
                 } else if (direction == ItemTouchHelper.LEFT) {
-                    Log.d(SNACKOOS, "Gonna delete this todo list");
-                    deleteTodoItem((String)viewHolder.itemView.getTag());
+                    mPersistence.deleteTodoList((String)viewHolder.itemView.getTag());
                 }
             }
         }).attachToRecyclerView(recyclerView);
 
-        // Prepare our Firebase Reference and the primary listener (SNACKOOS).
-        mPersistence = PersistenceFactory.getPersistence(this);
-        myFirebaseRef = new Firebase(FIREBASE_EXAMPLE_URL);
-        setUpSnackoos();
-    }
-
-    // Set the visibility based on what the adapter thinks is the visible item count.
-    private void setEmptyVisiblity() {
-        View v = findViewById(R.id.empty);
-        v.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
-    }
-
-    // Any time a child of SNACKOOS is added/changed/removed, we mirror the changes locally.
-    private void setUpSnackoos() {
-        snackoosEventListener = firebaseListReference().addChildEventListener(new ChildEventListener() {
+        mPersistence = PersistenceFactory.getMainPersistence(this, new ListEventListener<TodoList>() {
             @Override
-            public void onChildAdded(DataSnapshot dataSnapshot, String prevKey) {
-                String fbKey = dataSnapshot.getKey();
-                TodoList todoList = dataSnapshot.getValue(TodoList.class);
-                todoList.setKey(fbKey);
-
-                // Insert in order.
-                snackoosList.insertInOrder(todoList);
-
+            public void onInsert(TodoList item) {
+                snackoosList.insertInOrder(item);
                 adapter.notifyDataSetChanged();
 
                 // TODO(alexfandrianto): In order to capture the computed values for this TodoList,
@@ -136,47 +101,27 @@
             }
 
             @Override
-            public void onChildChanged(DataSnapshot dataSnapshot, String prevKey) {
-                String fbKey = dataSnapshot.getKey();
-                TodoList todoList = dataSnapshot.getValue(TodoList.class);
-                todoList.setKey(fbKey);
-
-                snackoosList.updateInOrder(todoList);
+            public void onUpdate(TodoList item) {
+                snackoosList.updateInOrder(item);
                 adapter.notifyDataSetChanged();
             }
 
             @Override
-            public void onChildRemoved(DataSnapshot dataSnapshot) {
-                String fbKey = dataSnapshot.getKey();
-                snackoosList.removeByKey(fbKey);
+            public void onDelete(String key) {
+                snackoosList.removeByKey(key);
                 adapter.notifyDataSetChanged();
 
                 // TODO(alexfandrianto): Stop watching the Task data for this TodoList.
 
                 setEmptyVisiblity();
             }
-
-            @Override
-            public void onChildMoved(DataSnapshot dataSnapshot, String prevKey) {
-
-            }
-
-            @Override
-            public void onCancelled(FirebaseError firebaseError) {
-
-            }
         });
     }
 
-    private Firebase firebaseListReference() {
-        return myFirebaseRef.child(SNACKOOS);
-    }
-    public void addTodoItem(String todo) {
-        firebaseListReference().push().setValue(new TodoList(todo));
-    }
-
-    public void deleteTodoItem(String fbKey) {
-        firebaseListReference().child(fbKey).removeValue();
+    // Set the visibility based on what the adapter thinks is the visible item count.
+    private void setEmptyVisiblity() {
+        View v = findViewById(R.id.empty);
+        v.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
     }
 
     public void addCallback(View view) {
@@ -187,14 +132,15 @@
                 .setView(todoItem)
                 .setPositiveButton("Add", new DialogInterface.OnClickListener() {
                     public void onClick(DialogInterface dialog, int whichButton) {
-                        addTodoItem(todoItem.getText().toString());
+                        mPersistence.addTodoList(new TodoList(todoItem.getText().toString()));
                     }
                 })
                 .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                     public void onClick(DialogInterface dialog, int whichButton) {
                     }
                 }).show();
-        dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+        dialog.getWindow().setSoftInputMode(
+                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
     }
 
     // The following methods are boilerplate for handling the Menu in the top right corner.
diff --git a/projects/todos/app/src/main/java/io/v/todos/Task.java b/projects/todos/app/src/main/java/io/v/todos/Task.java
index 98b6146..79698d9 100644
--- a/projects/todos/app/src/main/java/io/v/todos/Task.java
+++ b/projects/todos/app/src/main/java/io/v/todos/Task.java
@@ -12,7 +12,7 @@
  * @author alexfandrianto
  */
 @JsonIgnoreProperties({ "key" })
-public class Task extends KeyedData<Task> {
+public class Task implements KeyedData<Task> {
     private String text;
     private long addedAt;
     private boolean done;
diff --git a/projects/todos/app/src/main/java/io/v/todos/TodoList.java b/projects/todos/app/src/main/java/io/v/todos/TodoList.java
index 0361473..31f27a1 100644
--- a/projects/todos/app/src/main/java/io/v/todos/TodoList.java
+++ b/projects/todos/app/src/main/java/io/v/todos/TodoList.java
@@ -12,7 +12,7 @@
  * @author alexfandrianto
  */
 @JsonIgnoreProperties({ "numCompleted", "numTasks", "done", "key" })
-public class TodoList extends KeyedData<TodoList> {
+public class TodoList implements KeyedData<TodoList> {
     private String name;
     private long updatedAt;
 
diff --git a/projects/todos/app/src/main/java/io/v/todos/TodoListActivity.java b/projects/todos/app/src/main/java/io/v/todos/TodoListActivity.java
index 00535a8..54403de 100644
--- a/projects/todos/app/src/main/java/io/v/todos/TodoListActivity.java
+++ b/projects/todos/app/src/main/java/io/v/todos/TodoListActivity.java
@@ -11,7 +11,6 @@
 import android.support.v7.app.AlertDialog;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.helper.ItemTouchHelper;
-import android.util.Log;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
@@ -19,14 +18,9 @@
 import android.widget.EditText;
 import android.widget.Toolbar;
 
-import com.firebase.client.ChildEventListener;
-import com.firebase.client.DataSnapshot;
-import com.firebase.client.Firebase;
-import com.firebase.client.FirebaseError;
-import com.firebase.client.ValueEventListener;
-
-import io.v.todos.persistence.Persistence;
 import io.v.todos.persistence.PersistenceFactory;
+import io.v.todos.persistence.TodoListListener;
+import io.v.todos.persistence.TodoListPersistence;
 
 /**
  * TodoListActivity for Vanadium TODOs
@@ -41,11 +35,8 @@
  * @author alexfandrianto
  */
 public class TodoListActivity extends Activity {
-    private Persistence mPersistence;
-    private Firebase myFirebaseRef;
+    private TodoListPersistence mPersistence;
 
-    private final static String SNACKOO_LISTS = "snackoo lists (Task)";
-    private String snackooKey;
     private TodoList snackoo;
     private DataList<Task> snackoosList = new DataList<Task>();
     private boolean showDone = false; // TODO(alexfandrianto): Load from shared preferences...
@@ -53,13 +44,9 @@
     // This adapter handle mirrors the firebase list values and generates the corresponding todo
     // item View children for a list view.
     private TaskRecyclerAdapter adapter;
-    private ValueEventListener snackooEventListener;
-    private ChildEventListener snackoosEventListener;
 
     @Override
     protected void onDestroy() {
-        myFirebaseRef.removeEventListener(snackooEventListener);
-        myFirebaseRef.removeEventListener(snackoosEventListener);
         mPersistence.close();
         super.onDestroy();
     }
@@ -69,13 +56,12 @@
         setContentView(R.layout.activity_main);
 
         Intent intent = getIntent();
-        snackooKey = intent.getStringExtra(MainActivity.INTENT_SNACKOO_KEY);
+        String snackooKey = intent.getStringExtra(MainActivity.INTENT_SNACKOO_KEY);
 
         Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
         setActionBar(toolbar);
 
         // Set up the todo list adapter
-        final Activity self = this;
         adapter = new TaskRecyclerAdapter(snackoosList, new View.OnClickListener() {
             @Override
             public void onClick(View view) {
@@ -92,46 +78,44 @@
             @Override
             public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int direction) {
                 if (direction == ItemTouchHelper.RIGHT) {
-                    Log.d(SNACKOO_LISTS, "Gonna mark all tasks as done");
                     markAsDone((String)viewHolder.itemView.getTag());
                 } else if (direction == ItemTouchHelper.LEFT) {
-                    Log.d(SNACKOO_LISTS, "Gonna delete this todo list");
                     deleteTodoItem((String)viewHolder.itemView.getTag());
                 }
             }
         }).attachToRecyclerView(recyclerView);
 
-        // Prepare our Firebase Reference and the primary listener (SNACKOOS).
-        mPersistence = PersistenceFactory.getPersistence(this);
-        myFirebaseRef = new Firebase(MainActivity.FIREBASE_EXAMPLE_URL);
-        setUpSnackoo();
-        setUpSnackoos();
-    }
-
-    private Firebase firebaseListReference() {
-        return myFirebaseRef.child(MainActivity.SNACKOOS).child(snackooKey);
-    }
-
-    private Firebase firebaseTasksReference() {
-        return myFirebaseRef.child(SNACKOO_LISTS).child(snackooKey);
-    }
-
-    private void setUpSnackoo() {
-        snackooEventListener = firebaseListReference().addValueEventListener(new ValueEventListener() {
+        mPersistence = PersistenceFactory.getTodoListPersistence(this, snackooKey,
+                new TodoListListener() {
             @Override
-            public void onDataChange(DataSnapshot dataSnapshot) {
-                snackoo = dataSnapshot.getValue(TodoList.class);
-                if (snackoo == null) {
-                    // The list has been deleted. Get the heck out of here!
-                    finish();
-                    return;
-                }
+            public void onChange(TodoList value) {
+                snackoo = value;
                 getActionBar().setTitle(snackoo.getName());
             }
 
             @Override
-            public void onCancelled(FirebaseError firebaseError) {
+            public void onDelete() {
+                finish();
+            }
 
+            @Override
+            public void onInsert(Task item) {
+                snackoosList.insertInOrder(item);
+                adapter.notifyDataSetChanged();
+                setEmptyVisiblity();
+            }
+
+            @Override
+            public void onUpdate(Task item) {
+                snackoosList.updateInOrder(item);
+                adapter.notifyDataSetChanged();
+            }
+
+            @Override
+            public void onDelete(String key) {
+                snackoosList.removeByKey(key);
+                adapter.notifyDataSetChanged();
+                setEmptyVisiblity();
             }
         });
     }
@@ -142,71 +126,27 @@
         v.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
     }
 
-    // Any time a child of SNACKOOS is added/changed/removed, we mirror the changes locally.
-    private void setUpSnackoos() {
-        snackoosEventListener = firebaseTasksReference().addChildEventListener(new ChildEventListener() {
-            @Override
-            public void onChildAdded(DataSnapshot dataSnapshot, String prevKey) {
-                String fbKey = dataSnapshot.getKey();
-                Task task = dataSnapshot.getValue(Task.class);
-                task.setKey(fbKey);
-                snackoosList.insertInOrder(task);
-                adapter.notifyDataSetChanged();
-
-                setEmptyVisiblity();
-            }
-
-            @Override
-            public void onChildChanged(DataSnapshot dataSnapshot, String prevKey) {
-                String fbKey = dataSnapshot.getKey();
-                Task task = dataSnapshot.getValue(Task.class);
-                task.setKey(fbKey);
-                snackoosList.updateInOrder(task);
-                adapter.notifyDataSetChanged();
-            }
-
-            @Override
-            public void onChildRemoved(DataSnapshot dataSnapshot) {
-                String fbKey = dataSnapshot.getKey();
-                snackoosList.removeByKey(fbKey);
-                adapter.notifyDataSetChanged();
-
-                setEmptyVisiblity();
-            }
-
-            @Override
-            public void onChildMoved(DataSnapshot dataSnapshot, String prevKey) {
-
-            }
-
-            @Override
-            public void onCancelled(FirebaseError firebaseError) {
-
-            }
-        });
-    }
-
     public void addTodoItem(String todo) {
         // TODO(alexfandrianto): Turns out these are all batch changes that change the parents updatedAt
-        firebaseTasksReference().push().setValue(new Task(todo));
+        mPersistence.addTask(new Task(todo));
     }
 
     public void updateTodoItem(String fbKey, String todo) {
         // TODO(alexfandrianto): Turns out these are all batch changes that change the parents updatedAt
         Task task = snackoosList.findByKey(fbKey).copy();
         task.setText(todo);
-        firebaseTasksReference().child(fbKey).setValue(task);
+        mPersistence.updateTask(task);
     }
     public void markAsDone(String fbKey) {
         // TODO(alexfandrianto): Turns out these are all batch changes that change the parents updatedAt
         Task task = snackoosList.findByKey(fbKey).copy();
         task.setDone(!task.getDone());
-        firebaseTasksReference().child(fbKey).setValue(task);
+        mPersistence.updateTask(task);
     }
 
     public void deleteTodoItem(String fbKey) {
         // TODO(alexfandrianto): Turns out these are all batch changes that change the parents updatedAt
-        firebaseTasksReference().child(fbKey).removeValue();
+        mPersistence.deleteTask(fbKey);
     }
 
     public void addCallback(View view) {
@@ -224,7 +164,8 @@
                     public void onClick(DialogInterface dialog, int whichButton) {
                     }
                 }).show();
-        dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+        dialog.getWindow().setSoftInputMode(
+                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
     }
 
     private void initiateTaskEdit(final String fbKey) {
@@ -248,7 +189,8 @@
                     public void onClick(DialogInterface dialog, int whichButton) {
                     }
                 }).show();
-        dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+        dialog.getWindow().setSoftInputMode(
+                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
     }
 
     private void initiateTodoListEdit() {
@@ -272,16 +214,17 @@
                     public void onClick(DialogInterface dialog, int whichButton) {
                     }
                 }).show();
-        dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+        dialog.getWindow().setSoftInputMode(
+                WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
     }
 
 
     public void updateTodoList(String todo) {
-        firebaseListReference().setValue(new TodoList(todo));
+        mPersistence.updateTodoList(new TodoList(todo));
     }
 
     public void deleteTodoList() {
-        firebaseListReference().removeValue();
+        mPersistence.deleteTodoList();
     }
 
 
diff --git a/projects/todos/app/src/main/java/io/v/todos/persistence/FirebaseChildEventListenerAdapter.java b/projects/todos/app/src/main/java/io/v/todos/persistence/FirebaseChildEventListenerAdapter.java
new file mode 100644
index 0000000..c38d01e
--- /dev/null
+++ b/projects/todos/app/src/main/java/io/v/todos/persistence/FirebaseChildEventListenerAdapter.java
@@ -0,0 +1,48 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.todos.persistence;
+
+import com.firebase.client.ChildEventListener;
+import com.firebase.client.DataSnapshot;
+import com.firebase.client.FirebaseError;
+
+import io.v.todos.KeyedData;
+
+public class FirebaseChildEventListenerAdapter<T extends KeyedData> implements ChildEventListener {
+    private final Class<T> mType;
+    private final ListEventListener<T> mDelegate;
+
+    public FirebaseChildEventListenerAdapter(Class<T> type, ListEventListener<T> delegate) {
+        mType = type;
+        mDelegate = delegate;
+    }
+
+    @Override
+    public void onChildAdded(DataSnapshot dataSnapshot, String prevKey) {
+        T value = dataSnapshot.getValue(mType);
+        value.setKey(dataSnapshot.getKey());
+        mDelegate.onInsert(value);
+    }
+
+    @Override
+    public void onChildChanged(DataSnapshot dataSnapshot, String prevKey) {
+        T value = dataSnapshot.getValue(mType);
+        value.setKey(dataSnapshot.getKey());
+        mDelegate.onUpdate(value);
+    }
+
+    @Override
+    public void onChildRemoved(DataSnapshot dataSnapshot) {
+        mDelegate.onDelete(dataSnapshot.getKey());
+    }
+
+    @Override
+    public void onChildMoved(DataSnapshot dataSnapshot, String prevKey) {
+    }
+
+    @Override
+    public void onCancelled(FirebaseError firebaseError) {
+    }
+}
diff --git a/projects/todos/app/src/main/java/io/v/todos/persistence/FirebaseMain.java b/projects/todos/app/src/main/java/io/v/todos/persistence/FirebaseMain.java
new file mode 100644
index 0000000..711e8c4
--- /dev/null
+++ b/projects/todos/app/src/main/java/io/v/todos/persistence/FirebaseMain.java
@@ -0,0 +1,43 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.todos.persistence;
+
+import android.content.Context;
+
+import com.firebase.client.ChildEventListener;
+import com.firebase.client.Firebase;
+
+import io.v.todos.TodoList;
+
+public class FirebaseMain extends FirebasePersistence implements MainPersistence {
+    public static final String TODO_LISTS = "snackoos (TodoList)";
+
+    private final Firebase mTodoLists;
+    private final ChildEventListener mTodoListsListener;
+
+    public FirebaseMain(Context context, final ListEventListener<TodoList> listener) {
+        super(context);
+
+        mTodoLists = getFirebase().child(TODO_LISTS);
+
+        mTodoListsListener = mTodoLists.addChildEventListener(
+                new FirebaseChildEventListenerAdapter<>(TodoList.class, listener));
+    }
+
+    @Override
+    public void addTodoList(TodoList todoList) {
+        mTodoLists.push().setValue(todoList);
+    }
+
+    @Override
+    public void deleteTodoList(String key) {
+        mTodoLists.child(key).removeValue();
+    }
+
+    @Override
+    public void close() {
+        getFirebase().removeEventListener(mTodoListsListener);
+    }
+}
diff --git a/projects/todos/app/src/main/java/io/v/todos/persistence/FirebasePersistence.java b/projects/todos/app/src/main/java/io/v/todos/persistence/FirebasePersistence.java
index cd208c0..fbc8180 100644
--- a/projects/todos/app/src/main/java/io/v/todos/persistence/FirebasePersistence.java
+++ b/projects/todos/app/src/main/java/io/v/todos/persistence/FirebasePersistence.java
@@ -14,13 +14,21 @@
  *
  * @author alexfandrianto
  */
-public class FirebasePersistence implements Persistence {
+public abstract class FirebasePersistence implements Persistence {
+    private static final String FIREBASE_EXAMPLE_URL = "https://vivid-heat-7354.firebaseio.com/";
+
     static {
         // Set up Firebase to persist data locally even when offline. This must be set before
         // Firebase is used.
         Firebase.getDefaultConfig().setPersistenceEnabled(true);
     }
 
+    private final Firebase mFirebase;
+
+    protected Firebase getFirebase() {
+        return mFirebase;
+    }
+
     /**
      * Instantiates a persistence object that can be used to manipulate data.
      *
@@ -30,10 +38,7 @@
         // This no-ops if the context has already been set, and calls getApplicationContext so we
         // don't have to worry about leaking activity contexts.
         Firebase.setAndroidContext(context);
-    }
 
-    @Override
-    public void close() {
-        // TODO(rosswang): Remove listeners.
+        mFirebase = new Firebase(FIREBASE_EXAMPLE_URL);
     }
 }
diff --git a/projects/todos/app/src/main/java/io/v/todos/persistence/FirebaseTodoList.java b/projects/todos/app/src/main/java/io/v/todos/persistence/FirebaseTodoList.java
new file mode 100644
index 0000000..000cc47
--- /dev/null
+++ b/projects/todos/app/src/main/java/io/v/todos/persistence/FirebaseTodoList.java
@@ -0,0 +1,82 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.todos.persistence;
+
+import android.content.Context;
+
+import com.firebase.client.ChildEventListener;
+import com.firebase.client.DataSnapshot;
+import com.firebase.client.Firebase;
+import com.firebase.client.FirebaseError;
+import com.firebase.client.ValueEventListener;
+
+import io.v.todos.Task;
+import io.v.todos.TodoList;
+
+public class FirebaseTodoList extends FirebasePersistence implements TodoListPersistence {
+    public static final String TASKS = "snackoo lists (Task)";
+
+    private final Firebase mTodoList, mTasks;
+    private final ValueEventListener mTodoListListener;
+    private final ChildEventListener mTasksListener;
+
+    public FirebaseTodoList(Context context, String todoListKey, final TodoListListener listener) {
+        super(context);
+
+        mTodoList = getFirebase().child(FirebaseMain.TODO_LISTS).child(todoListKey);
+        mTasks = getFirebase().child(TASKS).child(todoListKey);
+
+        mTodoListListener = mTodoList.addValueEventListener(new ValueEventListener() {
+            @Override
+            public void onDataChange(DataSnapshot dataSnapshot) {
+                TodoList todoList = dataSnapshot.getValue(TodoList.class);
+                if (todoList == null) {
+                    listener.onDelete();
+                } else {
+                    listener.onChange(todoList);
+                }
+            }
+
+            @Override
+            public void onCancelled(FirebaseError firebaseError) {
+
+            }
+        });
+
+        mTasksListener = mTasks.addChildEventListener(
+                new FirebaseChildEventListenerAdapter<>(Task.class, listener));
+    }
+
+    @Override
+    public void updateTodoList(TodoList todoList) {
+        mTodoList.setValue(todoList);
+    }
+
+    @Override
+    public void deleteTodoList() {
+        mTodoList.removeValue();
+    }
+
+    @Override
+    public void addTask(Task task) {
+        mTasks.push().setValue(task);
+    }
+
+    @Override
+    public void updateTask(Task task) {
+        mTasks.child(task.getKey()).setValue(task);
+    }
+
+    @Override
+    public void deleteTask(String key) {
+        mTasks.child(key).removeValue();
+    }
+
+    @Override
+    public void close() {
+        getFirebase().removeEventListener(mTodoListListener);
+        getFirebase().removeEventListener(mTasksListener);
+    }
+}
diff --git a/projects/todos/app/src/main/java/io/v/todos/persistence/ListEventListener.java b/projects/todos/app/src/main/java/io/v/todos/persistence/ListEventListener.java
new file mode 100644
index 0000000..0e0641e
--- /dev/null
+++ b/projects/todos/app/src/main/java/io/v/todos/persistence/ListEventListener.java
@@ -0,0 +1,11 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.todos.persistence;
+
+public interface ListEventListener<T> {
+    void onInsert(T item);
+    void onUpdate(T item);
+    void onDelete(String key);
+}
diff --git a/projects/todos/app/src/main/java/io/v/todos/persistence/MainPersistence.java b/projects/todos/app/src/main/java/io/v/todos/persistence/MainPersistence.java
new file mode 100644
index 0000000..1fbbef5
--- /dev/null
+++ b/projects/todos/app/src/main/java/io/v/todos/persistence/MainPersistence.java
@@ -0,0 +1,12 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.todos.persistence;
+
+import io.v.todos.TodoList;
+
+public interface MainPersistence extends Persistence {
+    void addTodoList(TodoList todoList);
+    void deleteTodoList(String key);
+}
diff --git a/projects/todos/app/src/main/java/io/v/todos/persistence/PersistenceFactory.java b/projects/todos/app/src/main/java/io/v/todos/persistence/PersistenceFactory.java
index 104c180..9a34a11 100644
--- a/projects/todos/app/src/main/java/io/v/todos/persistence/PersistenceFactory.java
+++ b/projects/todos/app/src/main/java/io/v/todos/persistence/PersistenceFactory.java
@@ -6,16 +6,30 @@
 
 import android.content.Context;
 
+import io.v.todos.TodoList;
+
 public final class PersistenceFactory {
     private PersistenceFactory(){}
 
     /**
-     * Instantiates a persistence object that can be used to manipulate data.
+     * Instantiates a persistence object that can be used to manipulate todo lists.
      *
      * @param context an Android context, usually from an Android activity or application
      */
-    public static Persistence getPersistence(Context context) {
+    public static MainPersistence getMainPersistence(Context context,
+                                                     ListEventListener<TodoList> listener) {
         // TODO(rosswang): Choose this by build variant.
-        return new FirebasePersistence(context);
+        return new FirebaseMain(context, listener);
+    }
+
+    /**
+     * Instantiates a persistence object that can be used to manipulate a todo list.
+     *
+     * @param context an Android context, usually from an Android activity or application
+     */
+    public static TodoListPersistence getTodoListPersistence(Context context, String key,
+                                                             TodoListListener listener) {
+        // TODO(rosswang): Choose this by build variant.
+        return new FirebaseTodoList(context, key, listener);
     }
 }
diff --git a/projects/todos/app/src/main/java/io/v/todos/persistence/TodoListListener.java b/projects/todos/app/src/main/java/io/v/todos/persistence/TodoListListener.java
new file mode 100644
index 0000000..9632de9
--- /dev/null
+++ b/projects/todos/app/src/main/java/io/v/todos/persistence/TodoListListener.java
@@ -0,0 +1,13 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.todos.persistence;
+
+import io.v.todos.Task;
+import io.v.todos.TodoList;
+
+public interface TodoListListener extends ListEventListener<Task> {
+    void onChange(TodoList value);
+    void onDelete();
+}
diff --git a/projects/todos/app/src/main/java/io/v/todos/persistence/TodoListPersistence.java b/projects/todos/app/src/main/java/io/v/todos/persistence/TodoListPersistence.java
new file mode 100644
index 0000000..8ec2117
--- /dev/null
+++ b/projects/todos/app/src/main/java/io/v/todos/persistence/TodoListPersistence.java
@@ -0,0 +1,16 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.todos.persistence;
+
+import io.v.todos.Task;
+import io.v.todos.TodoList;
+
+public interface TodoListPersistence extends Persistence {
+    void updateTodoList(TodoList todoList);
+    void deleteTodoList();
+    void addTask(Task task);
+    void updateTask(Task task);
+    void deleteTask(String key);
+}