我开发了很多年的Java SE软件,而我曾经用过MVC设计模式.现在我开发Android应用程序,我不满意的说,android已经使用一个MVC模式,xml文件作为视图.
我在网上做了很多研究,但是似乎并没有一致的这个话题.有些使用MVC模式,其他的是MVP模式,但我是我的意见,没有一致意见.
最近我买了一本书(Android Best Practices,from Godfrey Nolan,Onur Cinar and David Truxall),在第二章中,您可以找到MVC,MVVM和依赖注入模式.尝试所有这些后,我认为对于我的应用程序和我的工作模式,最好的是MVVM模式.
我发现这种模式在使用活动进行编程时非常容易使用,但是我在使用片段编程时如何使用它非常困惑.我将再现从“Android最佳实践”一书的网站下载的简单的“todo app”MVVM模式的例子.
视图(活动)
package com.example.mvvm; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; public class TodoActivity extends Activity { public static final String APP_TAG = "com.logicdrop.todos"; private ListView taskView; private Button btNewTask; private EditText etNewTask; private TaskListManager delegate; /*The View handles UI setup only. All event logic and delegation *is handled by the viewmodel. */ public static interface TaskListManager { //Through this interface the event logic is //passed off to the viewmodel. void registerTaskList(ListView list); void registerTaskAdder(View button,EditText input); } @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.delegate = new Todoviewmodel(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.delegate.registerTaskList(taskView); this.delegate.registerTaskAdder(btNewTask,etNewTask); } }
该模型
package com.example.mvvm; 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; final class TodoModel { //The Model should contain no logic specific to the view - only //logic necessary to provide a minimal API to the viewmodel. 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(); } /*Overrides are now done in the viewmodel. The Model only needs *to add/delete,and the viewmodel can handle the specific needs of the View. */ public void addEntry(ContentValues data) { this.storage.insert(TodoModel.TABLE_NAME,data); } public void deleteEntry(final String field_params) { this.storage.delete(TodoModel.TABLE_NAME,field_params,null); } public Cursor findAll() { //Model only needs to return an accessor. The viewmodel will handle //any logic accordingly. return this.storage.query(TodoModel.TABLE_NAME,new String[] { "title" },null); } }
package com.example.mvvm; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; import java.util.ArrayList; import java.util.List; public class Todoviewmodel implements TodoActivity.TaskListManager { /*The viewmodel acts as a delegate between the ToDoActivity (View) *and the ToDoProvider (Model). * The viewmodel receives references from the View and uses them * to update the UI. */ private TodoModel db_model; private List<String> tasks; private Context main_activity; private ListView taskView; private EditText newTask; public Todoviewmodel(Context app_context) { tasks = new ArrayList<String>(); main_activity = app_context; db_model = new TodoModel(app_context); } //Overrides to handle View specifics and keep Model straightforward. private void deleteTask(View view) { db_model.deleteEntry("title='" + ((TextView)view).getText().toString() + "'"); } private void addTask(View view) { final ContentValues data = new ContentValues(); data.put("title",((TextView)view).getText().toString()); db_model.addEntry(data); } private void deleteAll() { db_model.deleteEntry(null); } private List<String> getTasks() { final 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; } private void renderTodos() { //The viewmodel handles rendering and changes to the view's //data. The View simply provides a reference to its //elements. taskView.setAdapter(new ArrayAdapter<String>(main_activity,android.R.layout.simple_list_item_1,getTasks().toArray(new String[] {}))); } public void registerTaskList(ListView list) { this.taskView = list; //Keep reference for rendering later if (list.getAdapter() == null) //Show items at startup { renderTodos(); } list.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(final AdapterView<?> parent,final View view,final int position,final long id) { //Tapping on any item in the list will delete that item from the database and re-render the list deleteTask(view); renderTodos(); } }); } public void registerTaskAdder(View button,EditText input) { this.newTask = input; button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View view) { //Add task to database,re-render list,and clear the input addTask(newTask); renderTodos(); newTask.setText(""); } }); } }
问题是当我尝试在使用片段时再现这种模式时,我不知道如何进行.我可以为每个片段有一个视图模型和一个模型,还是仅包含这些片段的活动?
使用经典的片段化方法(片段是活动中的内部类),它很容易与活动进行交互,或访问片段管理器来进行更改,但是如果我将代码解耦,并放置我的逻辑程序外面的活动,我已经看到,我需要经常引用我的viewmodel中的活动(不涉及活动的意见,但参考活动本身).
或者,例如,假设具有片段的活动正在处理从意图而不是来自模型(数据库或休息服务)的数据.然后,我觉得我不需要一个模型.也许我可以在活动中收到意图时创建模型,但我觉得这是不正确的(视图不应该与模型有关,只有viewmodel …).
任何人都可以提供关于如何在使用片段时使用MVVM模式的解释?
提前致谢.
解决方法
该模型
你的模特儿不需要改变(使用MVVM的美丽:))
视图(片段)
稍微不一样. View(Fragment)在我的设置中引用了viewmodel(Activity).而不是初始化您的代理,如:
// Old way -> I don't like it this.delegate = new Todoviewmodel(this);
我建议你使用一个着名的Android模式:
@Override public void onAttach(final Activity activity) { super.onAttach(activity); try { delegate = (ITaskListManager) activity; } catch (ClassCastException ignore) { throw new IllegalStateException("Activity " + activity + " must implement ITaskListManager"); } } @Override public void onDetach() { delegate = sDummyDelegate; super.onDetach(); }
这样,您的View(Fragment)强制其附加的活动实现了ITaskListManager接口.当Fragment从Activity中分离出来时,一些默认实现被设置为委托.当您具有未附加到活动的片段的实例(是的,可能会发生)时,可以避免获取错误.
以下是我的ViewFragment的完整代码:
public class ViewFragment extends Fragment { private ListView taskView; private Button btNewTask; private EditText etNewTask; private ITaskListManager delegate; /** * Dummy delegate to avoid nullpointers when * the fragment is not attached to an activity */ private final ITaskListManager sDummyDelegate = new ITaskListManager() { @Override public void registerTaskList(final ListView list) { } @Override public void registerTaskAdder(final View button,final EditText input) { } }; /* * The View handles UI setup only. All event logic and delegation * is handled by the viewmodel. */ public static interface ITaskListManager { // Through this interface the event logic is // passed off to the viewmodel. void registerTaskList(ListView list); void registerTaskAdder(View button,EditText input); } @Override public void onAttach(final Activity activity) { super.onAttach(activity); try { delegate = (ITaskListManager) activity; } catch (ClassCastException ignore) { throw new IllegalStateException("Activity " + activity + " must implement ITaskListManager"); } } @Override public void onDetach() { delegate = sDummyDelegate; super.onDetach(); } @Override public View onCreateView(final LayoutInflater inflater,final ViewGroup container,final Bundle savedInstanceState) { View view = inflater.inflate(R.layout.activity_view_model,container,false); taskView = (ListView) view.findViewById(R.id.tasklist); btNewTask = (Button) view.findViewById(R.id.btNewTask); etNewTask = (EditText) view.findViewById(R.id.etNewTask); delegate.registerTaskList(taskView); delegate.registerTaskAdder(btNewTask,etNewTask); return view; } }
viewmodel(活动)
使用活动作为您的viewmodel几乎是一样的.相反,您只需要确保您在此处创建模型,并将您的View(Fragment)添加到活动中…
public class viewmodelActivity extends ActionBarActivity implements ITaskListManager { @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_view_model); if (savedInstanceState == null) { getSupportFragmentManager().beginTransaction().add(R.id.container,new ViewFragment()).commit(); } initviewmodel(); } @Override public boolean onCreateOptionsMenu(final Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.view_model,menu); return true; } @Override public boolean onOptionsItemSelected(final MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button,so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } private Model db_model; private List<String> tasks; private ListView taskView; private EditText newTask; /** * Initialize the viewmodel */ private void initviewmodel() { tasks = new ArrayList<String>(); db_model = new Model(this); } private void deleteTask(final View view) { db_model.deleteEntry("title='" + ((TextView) view).getText().toString() + "'"); } private void addTask(final View view) { final ContentValues data = new ContentValues(); data.put("title",((TextView) view).getText().toString()); db_model.addEntry(data); } private void deleteAll() { db_model.deleteEntry(null); } private List<String> getTasks() { final 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; } private void renderTodos() { // The viewmodel handles rendering and changes to the view's // data. The View simply provides a reference to its // elements. taskView.setAdapter(new ArrayAdapter<String>(this,getTasks().toArray(new String[] {}))); } @Override public void registerTaskList(final ListView list) { taskView = list; // Keep reference for rendering later if (list.getAdapter() == null) // Show items at startup { renderTodos(); } list.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(final AdapterView<?> parent,final long id) { // Tapping on any // item in the list // will delete that // item from the // database and // re-render the list deleteTask(view); renderTodos(); } }); } @Override public void registerTaskAdder(final View button,final EditText input) { newTask = input; button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(final View view) { // Add task to database,and clear the input addTask(newTask); renderTodos(); newTask.setText(""); } }); } }
额外
应在活动中处理添加新视图或不同视图.这是很好的,因为你现在可以监听配置更改,并在一个特殊的片段交换为不同的方向…