參考資料: Android Best Practices
這篇是一個範例如何將傳統的寫寫法變成MVC的架構
傳統寫法: there are two Java files we will work with, TodoActivity.java, shown in Listing 2-1,
and TodoProvider.java, which you’ll see in Listing 2-2.
Listing 2-1. TodoActivity.java
package com.logicdrop.todos;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.os.StrictMode;
public class TodoActivity extends Activity
{
public static final String APP_TAG = "com.logicdrop.todos";
private ListView taskView;
private Button btNewTask;
private EditText etNewTask;
private TodoProvider provider;
private OnClickListener handleNewTaskEvent = new OnClickListener()
{
@Override
public void onClick(final View view)
{
Log.d(APP_TAG, "add task click received");
TodoActivity.this.provider.addTask(TodoActivity.this
.getEditText()
.getText()
.toString());
TodoActivity.this.renderTodos();
}
};
@Override
protected void onStart()
{
super.onStart();
}
private void createPlaceholders()
{
this.getProvider().deleteAll();
if (this.getProvider().findAll().isEmpty())
{
List<String> beans = new ArrayList<String>();
for (int i = 0; i < 10; i++)
{
String title = "Placeholder " + i;
this.getProvider().addTask(title);
beans.add(title);
}
}
}
EditText getEditText()
{
return this.etNewTask;
}
private TodoProvider getProvider()
{
return this.provider;
}
private ListView getTaskView()
{
return this.taskView;
}
public void onCreate(final Bundle bundle)
{
super.onCreate(bundle);
this.setContentView(R.layout.main);
this.provider = new TodoProvider(this);
this.taskView = (ListView) this.findViewById(R.id.tasklist);
this.btNewTask = (Button) this.findViewById(R.id.btNewTask);
this.etNewTask = (EditText) this.findViewById(R.id.etNewTask);
this.btNewTask.setOnClickListener(this.handleNewTaskEvent);
this.showFloatVsIntegerDifference();
this.createPlaceholders();
this.renderTodos();
}
private void renderTodos()
{
List<String> beans = this.getProvider().findAll();
Log.d(APP_TAG, String.format("%d beans found", beans.size()));
this.getTaskView().setAdapter(
new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, beans
.toArray(new String[]
{})));
this.getTaskView().setOnItemClickListener(new OnItemClickListener()
{
@Override
public void onItemClick(final AdapterView<?> parent,
final View view, final int position, final long id)
{
Log.d(APP_TAG, String.format(
"item with id: %d and position: %d", id, position));
TextView v = (TextView) view;
TodoActivity.this.getProvider().deleteTask(
v.getText().toString());
TodoActivity.this.renderTodos();
}
});
}
}
TodoActivity.java controls the layout of the app, and TodoProvider.java, shown in Listing 2-2,
manages the data for the items you add to your list. In the app we’ve populated it with a list of initial
placeholder items.
Listing 2-2. TodoProvider.java
package com.logicdrop.todos;
import java.util.ArrayList;
import java.util.List;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import com.logicdrop.todos.TodoActivity;
public class TodoProvider
{
private static final String DB_NAME = "tasks";
private static final String TABLE_NAME = "tasks";
private static final int DB_VERSION = 1;
private static final String DB_CREATE_QUERY = "CREATE TABLE " + TABLE_NAME + " (id integer
primary key autoincrement, title text not null);";
private SQLiteDatabase storage;
private SQLiteOpenHelper helper;
public TodoProvider(final Context ctx)
{
this.helper = new SQLiteOpenHelper(ctx, DB_NAME, null, DB_VERSION)
{
@Override
public void onCreate(final SQLiteDatabase db)
{
db.execSQL(DB_CREATE_QUERY);
}
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion,
final int newVersion)
{
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
this.onCreate(db);
}
};
this.storage = this.helper.getWritableDatabase();
}
public synchronized void addTask(final String title)
{
ContentValues data = new ContentValues();
data.put("title", title);
this.storage.insert(TABLE_NAME, null, data);
}
public synchronized void deleteAll()
{
this.storage.delete(TABLE_NAME, null, null);
}
public synchronized void deleteTask(final long id)
{
this.storage.delete(TABLE_NAME, "id=" + id, null);
}
public synchronized void deleteTask(final String title)
{
this.storage.delete(TABLE_NAME, "title='" + title + "'", null);
}
public synchronized List<String> findAll()
{
Log.d(TodoActivity.APP_TAG, "findAll triggered");
List<String> tasks = new ArrayList<String>();
Cursor c = this.storage.query(TABLE_NAME, new String[] { "title" }, null, null, null, null, null);
if (c != null)
{
c.moveToFirst();
while (c.isAfterLast() == false)
{
tasks.add(c.getString(0));
c.moveToNext();
}
c.close();
}
return tasks;
}
}
MVC 架構設計Android
MVC (Model-View-Controller)
The Model
The MVC Model component, shown in Listing 2-10, largely replaces the ToDoProvider.java code from before.
Listing 2-10. MVC Model code
final class TodoModel
{
private static final String DB_NAME = "tasks";
private static final String TABLE_NAME = "tasks";
private static final int DB_VERSION = 1;
private static final String DB_CREATE_QUERY = "CREATE TABLE " + TodoModel.TABLE_NAME +
" (id integer primary key autoincrement, title text not null);";
private final SQLiteDatabase storage;
private final SQLiteOpenHelper helper;
public TodoModel(final Context ctx)
{
this.helper = new SQLiteOpenHelper(ctx, TodoModel.DB_NAME, null, TodoModel.DB_VERSION)
{
@Override
public void onCreate(final SQLiteDatabase db)
{
db.execSQL(TodoModel.DB_CREATE_QUERY);
}
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion,
final int newVersion)
{
db.execSQL("DROP TABLE IF EXISTS " + TodoModel.TABLE_NAME);
this.onCreate(db);
}
};
this.storage = this.helper.getWritableDatabase();
}
public void addEntry(ContentValues data)
{
this.storage.insert(TodoModel.TABLE_NAME, null, data);
}
public void deleteEntry(final String field_params)
{
this.storage.delete(TodoModel.TABLE_NAME, field_params, null);
}
public Cursor findAll()
{
Log.d(TodoActivity.APP_TAG, "findAll triggered");
final Cursor c = this.storage.query(TodoModel.TABLE_NAME, new String[]
{ "title" }, null, null, null, null, null);
return c;
}
}
The View
The View code in MVC, shown in Listing 2-11, is a modified version of the ToDoActivity.java
code from before. Any UI changes now happen here, and the control code is now moved to the
ToDoController.java file.
Listing 2-11. MVC View code
public class TodoActivity extends Activity
{
public static final String APP_TAG = "com.example.mvc";
private ListView taskView;
private Button btNewTask;
private EditText etNewTask;
/*Controller changes are transparent to the View. UI changes won't
*affect logic, and vice-versa. See below: the TodoModel has
* been replaced with the TodoController, and the View persists
* without knowledge that the implementation has changed.
*/
private TodoController provider;
private final OnClickListener handleNewTaskEvent = new OnClickListener()
{
@Override
public void onClick(final View view)
{
Log.d(APP_TAG, "add task click received");
TodoActivity.this.provider.addTask(TodoActivity.this
.etNewTask
.getText()
.toString());
TodoActivity.this.renderTodos();
}
};
@Override
protected void onStop()
{
super.onStop();
}
@Override
protected void onStart()
{
super.onStart();
}
@Override
public void onCreate(final Bundle bundle)
{
super.onCreate(bundle);
this.setContentView(R.layout.main);
this.provider = new TodoController(this);
this.taskView = (ListView) this.findViewById(R.id.tasklist);
this.btNewTask = (Button) this.findViewById(R.id.btNewTask);
this.etNewTask = (EditText) this.findViewById(R.id.etNewTask);
this.btNewTask.setOnClickListener(this.handleNewTaskEvent);
this.renderTodos();
}
private void renderTodos()
{
final List<String> beans = this.provider.getTasks();
Log.d(TodoActivity.APP_TAG, String.format("%d beans found", beans.size()));
this.taskView.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,
beans.toArray(new String[]
{})));
this.taskView.setOnItemClickListener(new OnItemClickListener()
{
@Override
public void onItemClick(final AdapterView<?> parent, final View view, final int
position, final long id)
{
Log.d(TodoActivity.APP_TAG, String.format("item with id: %d and position: %d", id,
position));
final TextView v = (TextView) view;
TodoActivity.this.provider.deleteTask(v.getText().toString());
TodoActivity.this.renderTodos();
}
});
}
}
The Controller
Shown in Listing 2-12, the controller binds the UI to the data but also creates a layer of separation
between the model and view code above. This interface between the two layers provides a
framework for the code to expand and for new developers to follow the MVC pattern to know what
new code belongs where.
Listing 2-12. MVC Controller code
public class TodoController {
/*The Controller provides data from the Model for the View to bind to the UI. */
private TodoModel db_model;
private List<String> tasks;
public TodoController(Context app_context)
{
tasks = new ArrayList<String>();
db_model = new TodoModel(app_context);
}
public void addTask(final String title)
{
final ContentValues data = new ContentValues();
data.put("title", title);
db_model.addEntry(data);
}
//Overrides to handle View specifics and keep Model straightforward.
public void deleteTask(final String title)
{
db_model.deleteEntry("title='" + title + "'");
}
public void deleteTask(final long id)
{
db_model.deleteEntry("id='" + id + "'");
}
public void deleteAll()
{
db_model.deleteEntry(null);
}
public List<String> getTasks()
{
Cursor c = db_model.findAll();
tasks.clear();
if (c != null)
{
c.moveToFirst();
while (c.isAfterLast() == false)
{
tasks.add(c.getString(0));
c.moveToNext();
}
c.close();
}
return tasks;
}
}
The layout for the To Do List screen is defined in the Layout.xml file, It is also shown in Listing 2-3.
Listing 2-3. Layout.xml
<?xml version="1.0" encoding="utf-8"?> (change to LinearLayout)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget31"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TableRow
android:id="@+id/row"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/tasklist"
android:orientation="horizontal" >
<EditText
android:id="@+id/etNewTask"
android:layout_width="200px"
android:layout_height="wrap_content"
android:text=""
android:textSize="18sp" >
</EditText>
<Button
android:id="@+id/btNewTask"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@+string/add_button_name" >
</Button>
</TableRow>
<ListView
android:id="@+id/tasklist"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" >
</ListView>
</RelativeLayout>