- mongodb - 在 MongoDB mapreduce 中,如何展平值对象?
- javascript - 对象传播与 Object.assign
- html - 输入类型 ="submit"Vs 按钮标签它们可以互换吗?
- sql - 使用 MongoDB 而不是 MS SQL Server 的优缺点
首先我请求为我的英语不好向你道歉。
我开发Java SE软件很多年了,我曾经使用过MVC设计模式。现在我开发 android 应用程序,我对说 android 已经使用 MVC 模式的说法不满意,xml 文件充当 View 。
我在网上做了很多研究,但似乎对这个话题并没有一致意见。有些使用 MVC 模式,有些使用 MVP 模式,但我个人认为,没有一致意见。
最近我买了一本书(Android Best Practices, from Godfrey Nolan, Onur Cinar and David Truxall),在第二章,你可以找到MVC、MVVM和依赖注入(inject)模式的解释。在尝试了所有这些之后,我认为对于我的应用程序和我的工作模式来说,最好的是 MVVM 模式。
我发现这种模式在使用 Activity 编程时非常容易使用,但是在使用 Fragment 编程时我对如何使用它感到困惑。我将重现应用于简单“todo app”的 MVVM 模式示例,该示例是从“Android 最佳实践”一书的网站下载的。
View ( Activity )
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);
}
}
View 模型
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("");
}
});
}
}
问题是,当我尝试在使用 fragment 时重现此模式时,我不知道如何继续。我可以为每个 fragment 或仅为包含这些 fragment 的 Activity 提供 View 模型和模型吗?
使用经典的fragment方式(fragment是activity内部的一个内部类),很容易和activity交互,或者访问fragment manager做修改,但是如果我解耦代码,把我的程序在 Activity 之外的逻辑,我发现我经常需要在我的 ViewModel 中引用 Activity (不是对 Activity View 的引用,而是对 Activity 本身的引用)。
或者,例如,假设带有 fragment 的 Activity 正在处理从 Intent 接收的数据,而不是来自模型(数据库或休息服务)的数据。然后,我觉得我不需要模型。也许我可以在activity中收到intent时创建模型,但我觉得这样不正确( View 不应该和模型有关系,只有viewmodel...)。
谁能给我解释一下在使用 fragment 时如何在android中使用MVVM模式?
提前致谢。
最佳答案
注意:以下内容已过时,我不再推荐。主要是因为在此设置中很难测试 Viewsmodel。查看 Google 架构蓝图。
旧答案:
就个人而言,我更喜欢另一种设置:
模型
你的模特。不需要更改(使用 MVVM 的美妙之处:))
View ( fragment )
略有不同。 View (Fragment) 在我的设置中有对 ViewModel ( Activity ) 的引用。而不是像这样初始化您的委托(delegate):
// 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 )强制它所附加的 Activity 实现 ITaskListManager 接口(interface)。当 Fragment 与 Activity 分离时,一些默认实现被设置为委托(delegate)。这可以防止在您拥有未附加到 Activity 的 fragment 实例时出现错误(是的,这可能会发生)。
这是我的 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( Activity )
使用 Activity 作为 ViewModel 几乎是一样的。相反,您只需要确保在此处创建模型,并将 View ( fragment )添加到 Activity 中...
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, android.R.layout.simple_list_item_1, 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 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();
}
});
}
@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, re-render list, and clear the input
addTask(newTask);
renderTodos();
newTask.setText("");
}
});
}
}
额外
应在 Activity 中处理添加新 View 或不同 View 。这很好,因为您现在可以监听配置更改,并为不同的方向交换一个特殊的 Fragment...
关于android - 如何将 Android MVVM 模式与 fragment 一起使用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22267099/
我有一个像这样的 fragment 栈 F1 -> F2 -> F3 -> F4 -> F5 现在我需要删除 F2、F3、F4 fragment 。 我需要如果我从 F5 fragment 按下后退按
我需要帮助来理解以下场景的工作原理以及如何实现结果。 我有一个名为 ShoppingCart 的类。它有一个名为 addItemsToShoppingCartFromPreviousOrder 的方法
我有一个包含 ViewPager 的 fragment 。这个 ViewPager 显然是由其他 Fragments 填充的。 我的问题是,ViewPager 中的 Fragment 是否有任何方法(
所以我正在实现一个 FragmentActivity 并尝试添加一个 fragment ,但是我遇到了多个问题。我以前做过这个,实际上我使用的代码与上一个项目(它工作的地方)相同,但由于某种原因它在这
Closed. This question needs details or clarity。它当前不接受答案。
我想将 Android X fragment (androidx.fragment.app.Fragment) 转换为 Android native fragment (android.app.Fra
假设我有一个包含 10 列文本类型 (20) 的 SQLite 表。 ListFragment 将从数据库中提取 4 列并使用 SimpleCursorAdapter 显示在列表中。 选择后,List
我有一个对话框 fragment ,我为延迟初始化创建了一个类。当我显示对话框时,它显示正常。但是,当我关闭对话框时,它崩溃的原因是: fragment 与 fragment 管理器无关。 我也尝试过
我想在 View 分页器更改为 fragment 时刷新该 fragment 。 package com.mcivisoft.rcbeam; import android.os.Bundle; imp
我有一个名为的 Activity MainActivity 我在容器 R.id.mainContainer 中添加了 fragment “BenefitFragment”。 在 BenefitFrag
所以我有这个 ClientListView,效果很好,可以显示客户,我可以单击一个客户并在右侧获取他们的详细信息(在我的第二个 fragment 中)。由此处的布局定义: 这很好用,但后来我
我试过这段代码,但点击第一个 fragment 中的按钮并没有改变第二个 fragment 中的字符串值。 这是第一个 fragment 的 kotlin 文件。它有两个按钮,点击它们可以更改字符串值
我有以下情况:我打开 fragment A 和目标,通过按钮的单击事件转到 fragment B。当我在 fragment B 中并点击后退按钮(为了返回 fragment A) 我想将一些参数传递给
我制作了一个 NavigationDrawer fragment ,其中包含 Home、Settings、Feedback 等项目。 home 项在点击 home 时应该打开,home 是在应用程序启
我正在一个接一个地替换 2 个 fragment ,两个 fragment 都有不同的选项菜单。当我替换第二个 fragment 时,它也显示第一个 fragment 的菜单。 setHasOptio
我有问题: android.app.Fragment$InstantiationException: Unable to instantiate fragment ${packageName}.${a
我正在研究 fragment 转换。当我用第二个 fragment 替换第一个 fragment 时,它出现在第一个 fragment 的下方。我希望它移动到第一个 fragment 之上。我该怎么做
我在抽屉导航里总共有 12 个 fragment 。每个 fragment 都有 volley 方法。每个 fragment 都显示自己的 Volley 响应,除了 position = 1 和 po
我在我的 Activity 中使用了两个 fragment 。最初我将向 Activity 添加一个 fragment 。然后在第一个 fragment 中使用监听器我想用第二个 fragment 替
我正在实现一个“fragments-101”程序,当相应的按钮被点击时我会替换一个 fragment 。然而,下面的事情发生了: 为什么会这样?为什么初始 fragment 没有被完全替换?MainA
我是一名优秀的程序员,十分优秀!