Сначала я прошу извиниться за мой не хороший английский.
Я разработал много лет программное обеспечение Java SE, и я использовал шаблон проектирования MVC. Теперь я разрабатываю приложения для Android, и я не доволен аргументом, в котором говорится, что андроид уже использует шаблон MVC, а файлы xml действуют как представление.
Я провел много исследований в Интернете, но, похоже, в этой теме нет единодушия. Некоторые используют шаблон MVC, другие - шаблон MVP, но, на мой взгляд, нет единодушия.
Недавно я купил книгу (Android Best Practices от Godfrey Nolan, Onur Cinar и David Truxall), а в главе 2 вы могут найти шаблоны MVC, MVVM и зависимостей впрыска. Попробовав их все, я думаю, что для моих приложений и моего режима работы лучшим является шаблон MVVM.
Я считаю, что этот шаблон очень прост в использовании при программировании с действиями, но я смущен тем, как его использовать при программировании с фрагментами. Я воспроизведу пример шаблона MVVM, применяемого к простому "todo app", загруженному с веб-сайта книги "Лучшие Android-книги".
Вид (активность)
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, null, 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, null, null, null, null);
}
}
ViewModel
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 с андроидом при использовании фрагментов?
Спасибо заранее.