如何使用带有片段的 Android MVVM 模式? [英] How to use Android MVVM pattern with fragments?

查看:34
本文介绍了如何使用带有片段的 Android MVVM 模式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先,我请求为我的英语不好而道歉.

First I request apologizes about my not to good English.

我已经开发了很多年的Java SE软件,并且我曾经使用过MVC设计模式.现在我开发 android 应用程序,我对 android 已经使用 MVC 模式的论点不满意,xml 文件充当视图.

I have developed a lot of years Java SE software, and I used to use the MVC design pattern. Now I develop android apps, and I'm not happy with the argument that says that android already uses an MVC pattern, with the xml files acting as the view.

我在网上做了很多研究,但似乎对这个话题并没有达成一致.有些使用MVC模式,有些使用MVP模式,但我认为,没有一致意见.

I did a lot of research on the web, but it seems that there is not unanimity about this topic. Some use the MVC pattern, others the MVP pattern, but I'm my opinion, there is no unanimity.

最近我买了一本书(Android 最佳实践,来自 Godfrey Nolan、Onur Cinar 和 David Truxall),在第二章中,你可以找到 MVC、MVVM 和依赖注入模式的解释.在尝试了所有这些之后,我认为对于我的应用程序和我的工作模式来说,最好的是 MVVM 模式.

Recently I bought a book (Android Best Practices, from Godfrey Nolan, Onur Cinar and David Truxall), and in the chapter two, you can find the MVC, the MVVM and the Dependency Injection patterns explained. After trying all of them, I think that for my apps and my work mode the best is the MVVM pattern.

我发现这种模式在用活动编程时很容易使用,但在用片段编程时我对如何使用它感到困惑.我将重现 MVVM 模式应用于简单todo 应用"的示例,该示例从Android 最佳实践"一书的网站上下载.

I find this pattern very easy to use when programming with activities, but I'm confused about how to use it when programming with fragments. I will reproduce the example of the MVVM pattern applied to simple "todo app", downloaded from the website of the "Android Best Practices" book.

视图(活动)

   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);
    }
   }

视图模型

 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("");
            }
        });
    }
   }

问题是,当我尝试在使用片段时重现此模式时,我不确定如何继续.我可以为每个片段创建一个视图模型和一个模型,还是只为包含这些片段的活动创建一个模型?

The problem is that when I try to reproduce this pattern when using fragments, I'm no sure how to proceed. May I have a view model and a model for each fragment or only for the activity that contains those fragments?

用经典的fragment方式(fragment是activity内部的一个内部类),很容易和activity交互,或者访问fragment manager做修改,但是如果我把代码解耦,把我的程序在活动之外的逻辑,我已经看到我经常需要引用我的 ViewModel 中的活动(不是对活动视图的引用,而是对活动本身的引用).

With the classic approach to fragment (the fragment is a inner class inside the activity), it is easy to interact with the activity, or to access the fragment manager to do changes, but if I decouple the code, and put the logic of my program outside the activity, I have seen that I need very often references to the activity in my ViewModel (not references to the views of the activity, but references to the activity itself).

或者例如,假设带有片段的活动正在处理从意图接收的数据,而不是从模型(数据库或休息服务)接收的数据.然后,我觉得我不需要模型.也许我可以在活动中收到意图时创建模型,但我觉得这不正确(视图应该与模型没有关系,只有视图模型......).

Or for example, imagine that that the activity with fragments, is working with data received from an intent, not from a model (database or rest service). Then, I feel that I don't need a model. Maybe I can create the model when I receive the intent in the activity, but I feel that this is not correct (the view should not have relation with the model, only the viewmodel...).

有人可以解释一下在使用片段时如何在 android 中使用 MVVM 模式吗?

May anybody offer me an explanation about how to use the MVVM pattern with android when using fragments?

提前致谢.

推荐答案

注意:以下内容已过时,我不再推荐它.主要是因为在这种设置中很难测试 Viewsmodel.看看谷歌架构蓝图.

NOTE: The following is outdated, and I would not recommend it anymore. Mainly because it's difficult to test the Viewsmodel in this setup. Have a look at Google Architecture Blueprints.

旧答案:

就我个人而言,我更喜欢另一种设置:

Personally, I prefer an alternate setup:

模型

你的模型.不需要更改(使用 MVVM 的美感 :) )

Your model. Doesn't need to be changed (beauty of using MVVM :) )

视图(片段)

略有不同.View (Fragment) 在我的设置中有对 ViewModel ( Activity ) 的引用.而不是像这样初始化您的委托:

Slightly different. The View (Fragment) has a reference to the ViewModel ( Activity ) in my setup. Instead of initializing your delegate like:

// Old way -> I don't like it
this.delegate = new TodoViewModel(this);

我建议您使用众所周知的 Android 模式:

I suggest you use a well-known Android pattern:

@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();
}

通过这种方式,您的视图(片段)强制其附加到的 Activity 实现 ITaskListManager 接口.当 Fragment 从 Activity 分离时,一些默认实现被设置为委托.这可以防止在您拥有未附加到 Activity 的片段实例时出现错误(是的,这可能发生).

This way, your View (Fragment) enforces that the Activity to which it is attached, implemets the ITaskListManager interface. When the Fragment is detached from the Activity, some default implementation is set as the delegate. This prevents getting errors when you have an instance of a fragment which is not attached to an Activity (yes, this can happen).

这是我的 ViewFragment 的完整代码:

Here's the complete code for my 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 作为您的 ViewModel 几乎相同.相反,您只需要确保在此处创建模型,并将您的视图(片段)添加到活动中...

Using an Activity as your ViewModel is almost the same. Instead, you only need to make sure that you create the Model here, and that you add your View (Fragment) to the 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("");
            }
        });
    }
}

额外

添加新视图或不同的视图应在活动中处理.这很好,因为您现在可以监听配置更改,并为不同的方向交换一个特殊的 Fragment...

Adding new Views, or different views, should be handled in the activity. This is nice, since you can now listen for configuration changes, and swap in a special Fragment for a different orientation...

这篇关于如何使用带有片段的 Android MVVM 模式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆