如何在Android上采用Model View Presenter

本文深入探讨了ModelViewPresenter(MVP)模式在Android应用中的实现细节,讲解了如何构建一个简单的Notes应用,以及MVP模式如何帮助解决Android架构带来的挑战。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在上一教程中 ,我们讨论了Model View Presenter模式,如何在Android上应用以及它最重要的优点是什么。 在本教程中,我们将通过在Android应用程序中实现Model View Presenter模式来进行更详细的研究。

在本教程中:

  • 我们使用MVP模式构建一个简单的应用程序
  • 我们探索了如何在Android上实现MVP模式
  • 我们讨论了如何克服Android架构所带来的一些困难

1.模型视图演示器

Model View Presenter模式是基于Model View Controller(MVC)模式的体系结构模式,该模式增加了关注点分离并促进了单元测试 。 它创建三个层, ModelViewPresenter ,每个层都有明确的职责。

模型视图演示者层

该模型包含应用程序的业务逻辑 。 它控制如何创建,存储和修改数据。 视图是一个被动界面,用于显示数据并将用户操作路由到Presenter。 演示者充当中间人。 它从模型中检索数据并将其显示在视图中。 它还处理视图转发的用户操作。

2.项目计划与设置

我们将构建一个简单的Notes应用程序来说明MVP。 该应用程序允许用户记录笔记,将其保存在本地数据库中以及删除笔记。 为简单起见,该应用程序只有一个活动。

应用布局

在本教程中,我们主要集中在MVP模式的实现上。 跳过了其他功能,例如,建立SQLite数据库,构造DAO或处理用户交互。 如果您需要任何有关这些主题的帮助,Envato Tuts +都提供了有关这些主题的出色教程。

动作图和MVP层

让我们从创建新笔记开始。 如果我们将此操作分解为较小的操作,则使用MVP架构模式的流程如下所示:

  • 用户键入注释,然后单击添加注释按钮。
  • 演示者使用用户输入的文本创建一个Note对象,并要求Model将其插入数据库中。
  • 模型将注释插入数据库中,并通知演示者注释列表已更改。
  • 演示者清除文本字段,并要求视图刷新其列表以显示新创建的笔记。
MVP行动图

MVP接口

现在,让我们考虑实现此操作所需的操作,并使用MVP将其分开。 为了使各种对象保持松散耦合 ,各层之间的通信通过使用接口进行。 我们需要四个接口:

  • RequiredViewOps :演示者可用的必需View操作
  • ProvidedPresenterOps :提供给View以便与Presenter通信的操作
  • RequiredPresenterOps :模型需要的Presenter操作
  • ProvidedModelOps :提供给Model的与Presenter通信的操作
MVP接口

3.

现在我们已经知道如何组织各种方法,我们可以开始创建我们的应用程序了。 通过仅关注添加新注释的操作,我们简化了实现。 本教程的源文件位于GitHub上

我们仅使用一个Activity ,其布局包括:

  • 新注释的EditText
  • Button以添加注释
  • RecyclerView列出所有注释
  • RecyclerView支架内的两个TextView元素和一个Button

介面

让我们从创建接口开始。 为了使一切井井有条,我们将接口放置在一个支架中。 同样,在此示例中,我们集中于添加新笔记的操作。

public interface MVP_Main {
    /**
     * Required View methods available to Presenter.
     * A passive layer, responsible to show data
     * and receive user interactions
     */
    interface RequiredViewOps {
    	// View operations permitted to Presenter
		Context getAppContext();
        	Context getActivityContext();
		void notifyItemInserted(int layoutPosition);
        	void notifyItemRangeChanged(int positionStart, int itemCount);
    }

    /**
     * Operations offered to View to communicate with Presenter.
     * Processes user interactions, sends data requests to Model, etc.
     */
    interface ProvidedPresenterOps {
       	// Presenter operations permitted to View
		void clickNewNote(EditText editText);
            // setting up recycler adapter
            int getNotesCount();
            NotesViewHolder createViewHolder(ViewGroup parent, int viewType);
            void bindViewHolder(NotesViewHolder holder, int position);
    }

    /**
     * Required Presenter methods available to Model.
     */
    interface RequiredPresenterOps {
       	// Presenter operations permitted to Model
		Context getAppContext();
        	Context getActivityContext();
    }

    /**
     * Operations offered to Model to communicate with Presenter
     * Handles all data business logic.
     */
    interface ProvidedModelOps {
        	// Model operations permitted to Presenter
            int getNotesCount();
            Note getNote(int position);
		int insertNote(Note note);
        	boolean loadData();
    }
}

查看层

现在是时候创建Model,View和Presenter图层了。 由于MainActivity将充当View,因此它应该实现RequiredViewOps接口。

public class MainActivity
        extends AppCompatActivity
    implements View.OnClickListener, MVP_Main.RequiredViewOps {

    private MVP_Main.ProvidedPresenterOps mPresenter;
    private EditText mTextNewNote;
    private ListNotes mListAdapter;

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.fab:{
                // Adds a new note
                mPresenter.clickNewNote(mTextNewNote);
            }
        }
    }
    @Override
    public Context getActivityContext() {
        return this;
    }

    @Override
    public Context getAppContext() {
        return getApplicationContext();
    }
    // Notify the RecyclerAdapter that a new item was inserted
    @Override
    public void notifyItemInserted(int adapterPos) {
        mListAdapter.notifyItemInserted(adapterPos);
    }
    // notify the RecyclerAdapter that items has changed
    @Override
    public void notifyItemRangeChanged(int positionStart, int itemCount){
        mListAdapter.notifyItemRangeChanged(positionStart, itemCount);
    }
    // notify the RecyclerAdapter that data set has changed
    @Override
    public void notifyDataSetChanged() {
        mListAdapter.notifyDataSetChanged();
    }
    // Recycler adapter
    // This class could have their own Presenter, but for the sake of
    // simplicity, will use only one Presenter.
    // The adapter is passive and all the processing occurs 
    // in the Presenter layer.
    private class ListNotes extends RecyclerView.Adapter<NotesViewHolder>       
    {
        @Override
        public int getItemCount() {
            return mPresenter.getNotesCount();
        }

        @Override
        public NotesViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return mPresenter.createViewHolder(parent, viewType);
        }

        @Override
        public void onBindViewHolder(NotesViewHolder holder, int position) {
            mPresenter.bindViewHolder(holder, position);
        }
    }
}

演示者层

演示者是中间人,需要实现两个接口:

  • ProvidedPresenterOps以允许从View呼叫
  • 从模型接收结果的RequiredPresenterOps

特别注意View层参考。 我们需要使用WeakReference<MVP_Main.RequiredViewOps>因为MainActivity可以随时被销毁,并且我们想避免内存泄漏。 另外,尚未设置“模型”层。 稍后,当我们将MVP层连接在一起时,我们便会这样做。

public class MainPresenter implements MVP_Main.ProvidedPresenterOps, MVP_Main.RequiredPresenterOps {

    // View reference. We use as a WeakReference
    // because the Activity could be destroyed at any time
    // and we don't want to create a memory leak
    private WeakReference<MVP_Main.RequiredViewOps> mView;
    // Model reference
    private MVP_Main.ProvidedModelOps mModel;

    /**
     * Presenter Constructor
     * @param view  MainActivity
     */
    public MainPresenter(MVP_Main.RequiredViewOps view) {
        mView = new WeakReference<>(view);
    }

    /**
     * Return the View reference.
     * Throw an exception if the View is unavailable.
     */
    private MVP_Main.RequiredViewOps getView() throws NullPointerException{
        if ( mView != null )
            return mView.get();
        else
            throw new NullPointerException("View is unavailable");
    }

    /**
     * Retrieves total Notes count from Model
     * @return  Notes list size
     */
    @Override
    public int getNotesCount() {
        return mModel.getNotesCount();
    }

    /**
     * Creates the RecyclerView holder and setup its view
     * @param parent    Recycler viewGroup
     * @param viewType  Holder type
     * @return          Recycler ViewHolder
     */
    @Override
    public NotesViewHolder createViewHolder(ViewGroup parent, int viewType) {
        NotesViewHolder viewHolder;
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());

        View viewTaskRow = inflater.inflate(R.layout.holder_notes, parent, false);
        viewHolder = new NotesViewHolder(viewTaskRow);

        return viewHolder;
    }

    /**
     * Binds ViewHolder with RecyclerView
     * @param holder    Holder to bind
     * @param position  Position on Recycler adapter
     */
    @Override
    public void bindViewHolder(final NotesViewHolder holder, int position) {
        final Note note = mModel.getNote(position);
        holder.text.setText( note.getText() );
        holder.date.setText( note.getDate() );
        holder.btnDelete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                clickDeleteNote(note, holder.getAdapterPosition(), holder.getLayoutPosition());
            }
        });

    }

    /**
     * @return  Application context
     */
    @Override
    public Context getAppContext() {
        try {
            return getView().getAppContext();
        } catch (NullPointerException e) {
            return null;
        }
    }

    /**
     * @return  Activity context
     */
    @Override
    public Context getActivityContext() {
        try {
            return getView().getActivityContext();
        } catch (NullPointerException e) {
            return null;
        }
    }

    /**
     * Called by View when user clicks on new Note button.
     * Creates a Note with text typed by the user and asks
     * Model to insert it in DB.
     * @param editText  EditText with text typed by user
     */
    @Override
    public void clickNewNote(final EditText editText) {
        getView().showProgress();
        final String noteText = editText.getText().toString();
        if ( !noteText.isEmpty() ) {
            new AsyncTask<Void, Void, Integer>() {
                @Override
                protected Integer doInBackground(Void... params) {
                    // Inserts note in Model, returning adapter position
                    return mModel.insertNote(makeNote(noteText));
                }

                @Override
                protected void onPostExecute(Integer adapterPosition) {
                    try {
                        if (adapterPosition > -1) {
                            // Note inserted
                            getView().clearEditText();
                            getView().notifyItemInserted(adapterPosition + 1);
                            getView().notifyItemRangeChanged(adapterPosition, mModel.getNotesCount());
                        } else {
                            // Informs about error
                            getView().hideProgress();
                            getView().showToast(makeToast("Error creating note [" + noteText + "]"));
                        }
                    } catch (NullPointerException e) {
                        e.printStackTrace();
                    }
                }
            }.execute();
        } else {
            try {
                getView().showToast(makeToast("Cannot add a blank note!"));
            } catch (NullPointerException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Creates a Note object with given text
     * @param noteText  String with Note text
     * @return  A Note object
     */
    public Note makeNote(String noteText) {
        Note note = new Note();
        note.setText( noteText );
        note.setDate(getDate());
        return note;

    }
}

模型层

模型层负责处理业务逻辑。 它包含一个ArrayList其中的注释已添加到数据库中,DAO引用执行数据库操作,以及对Presenter的引用。

public class MainModel implements MVP_Main.ProvidedModelOps {

    // Presenter reference
    private MVP_Main.RequiredPresenterOps mPresenter;
    private DAO mDAO;
    // Recycler data
    public ArrayList<Note> mNotes;

    /**
     * Main constructor, called by Activity during MVP setup
     * @param presenter Presenter instance
     */
    public MainModel(MVP_Main.RequiredPresenterOps presenter) {
        this.mPresenter = presenter;
        mDAO = new DAO( mPresenter.getAppContext() );
    }

     /**
     * Inserts a note on DB
     * @param note  Note to insert
     * @return      Note's position on ArrayList
     */
    @Override
    public int insertNote(Note note) {
        Note insertedNote = mDAO.insertNote(note);
        if ( insertedNote != null ) {
            loadData();
            return getNotePosition(insertedNote);
        }
        return -1;
    }

     /**
     * Loads all Data, getting notes from DB
     * @return  true with success
     */
    @Override
    public boolean loadData() {
        mNotes = mDAO.getAllNotes();
        return mNotes != null;
    }
     /**
     * Gets a specific note from notes list using its array position
     * @param position    Array position
     * @return            Note from list
     */
    @Override
    public Note getNote(int position) {
        return mNotes.get(position);
    }
     
    /**
     * Get ArrayList size
     * @return  ArrayList size
     */
    @Override
    public int getNotesCount() {
        if ( mNotes != null )
            return mNotes.size();
        return 0;
    }
}

4.捆绑在一起

设置好MVP层后,我们需要实例化它们并插入必要的引用。 在开始之前,我们需要解决一些与Android直接相关的问题。

实例化图层

由于Android不允许实例化Activity ,因此将为我们实例化View层。 我们负责实例化Presenter和Model层。 不幸的是,实例化Activity之外的那些层可能会遇到问题。

建议使用一种形式的依赖项注入来完成此操作。 由于我们的目标是专注于MVP实施,因此我们将采用一种更简单的方法。 这不是可用的最佳方法,但最容易理解。 我们将在本系列的稍后部分讨论MVP和依赖注入。

  • 使用局部变量在Activity中实例化Presenter和Model
  • 在Presenter中设置RequiredViewOpsProvidedModelOps
  • 在模型中设置RequiredPresenterOps
  • ProvidedPresenterOps保存为在视图中使用的参考
/**
* Setup Model View Presenter pattern
*/
private void setupMVP() {
     // Create the Presenter
     MainPresenter presenter = new MainPresenter(this);
     // Create the Model
     MainModel model = new MainModel(presenter);
     // Set Presenter model
     presenter.setModel(model);
     // Set the Presenter as a interface
     mPresenter = presenter;
}

处理配置更改

我们应该考虑的另一件事是活动的生命周期。 Android的Activity可以随时销毁,Presenter和Model层也可以随之销毁。 我们需要通过使用某种状态机来解决此问题,以在配置更改期间保存状态。 我们还应该将活动状态告知其他层。

为了实现这一点,我们将使用一个单独的类StateMaintainer ,该类包含一个Fragment,该Fragment保持其状态并使用该片段保存和检索我们的对象。 您可以在本教程源文件中查看此类的实现。

我们需要向Presenter和Model添加onDestroy方法,以通知他们有关Activity的当前状态。 我们还需要向Presenter添加setView方法,该方法将负责接收重新创建的Activity的新View引用。

查看生命周期
public class MainActivity
        extends AppCompatActivity
    implements View.OnClickListener, MVP_Main.RequiredViewOps
{
    // …
    private void setupMVP() {
        // Check if StateMaintainer has been created
        if (mStateMaintainer.firstTimeIn()) {
            // Create the Presenter
            MainPresenter presenter = new MainPresenter(this);
            // Create the Model
            MainModel model = new MainModel(presenter);
            // Set Presenter model
            presenter.setModel(model);
            // Add Presenter and Model to StateMaintainer
            mStateMaintainer.put(presenter);
            mStateMaintainer.put(model);

            // Set the Presenter as a interface
            // To limit the communication with it
            mPresenter = presenter;

        }
        // get the Presenter from StateMaintainer
        else {
            // Get the Presenter
            mPresenter = mStateMaintainer.get(MainPresenter.class.getName());
            // Updated the View in Presenter
            mPresenter.setView(this);
        }
    }
    // …
}

结论

MVP模式能够解决由Android的默认架构引起的一些问题。 它使您的代码易于维护和测试。 首先,采用MVP可能看起来很困难,但是一旦您了解了其背后的逻辑,整个过程就很简单。

现在,您可以创建自己的MVP库或使用现有的解决方案,例如Mosby或simple-mvp。 您现在应该更好地了解这些库在后台执行的操作。

我们的MVP旅程即将结束。 在本系列的第三部分和最后一部分中,我们将单元测试添加到混合中,并使我们的代码适应Dagger使用依赖注入。 我希望能在那里看到你。

翻译自: https://code.tutsplus.com/tutorials/how-to-adopt-model-view-presenter-on-android--cms-26206

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值