Android MVP模式实战练习之一步一步打造一款简易便笺app(二)

前情提要

上一篇 Android MVP模式实战练习之一步一步打造一款简易便笺app(一)
我们已经完成了主界面View和Presenter的编写,也设计好了数据Model的接口。如果你看完上一篇对V和P的设计还不是很清晰,没事,接下来我们要开始写编辑便笺界面的V和P,套路是一毛一样的,带你再走一遍。

编写编辑界面View和Presenter

跟之前一样,我们首先要创建一个EditActivity,并为其编写一个布局xml:

layout/edit_act:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?attr/colorPrimary"
            android:paddingTop="25dp"></android.support.v7.widget.Toolbar>
    </android.support.design.widget.AppBarLayout>

    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <FrameLayout
            android:id="@+id/fragment_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="16dp"
            app:fabSize="normal"
            app:layout_anchor="@id/fragment_content"
            app:layout_anchorGravity="bottom|end"
            android:src="@drawable/save"/>
    </android.support.design.widget.CoordinatorLayout>

</LinearLayout>

基本跟MainActivity的布局一样,就不多说了。然后看看EditActivity是怎么写的:

EditActivity:

public class EditActivity extends AppCompatActivity {

    public static final String EXTRA_NOTE_ID = "NOTE_ID";
    private Toolbar toolbar;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.edit_act);
        //5.0以上使布局延伸到状态栏的方法
        View decorView = getWindow().getDecorView();
        int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
        decorView.setSystemUiVisibility(option);
        getWindow().setStatusBarColor(Color.TRANSPARENT);

        //设置toolbar
        toolbar = (Toolbar) findViewById(R.id.toolbar);
        toolbar.setTitle("编辑便笺");
        setSupportActionBar(toolbar);
        ActionBar ab = getSupportActionBar();
        ab.setDisplayHomeAsUpEnabled(true);

        String noteId = getIntent().getStringExtra(EXTRA_NOTE_ID); //为空则代表是新建的便笺

        //创建fragment (V)
        EditFragment editFragment = (EditFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_content);
        if(editFragment == null){
            editFragment = EditFragment.getInstence();
            ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),editFragment,R.id.fragment_content);
        }

        //创建Presenter  (P)
        EditPresenter presenter = new EditPresenter(Injection.provideRespository(this),editFragment,noteId);


    }


    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()){
            case android.R.id.home:
                finish();
                break;
        }
        return super.onOptionsItemSelected(item);
    }
}

可以看到跟MainActivity的套路是差不多的,不过我们在创建编辑界面的V和P之前,通过 String noteId = getIntent().getStringExtra(EXTRA_NOTE_ID); 这么一行代码来获取了MainActivity传递过来的数据。我们打开MainFragment里看看,里面有这么两个方法:

  @Override
    public void showAddNotesUi() {
        Intent i = new Intent(getActivity(), EditActivity.class);
        startActivity(i);
    }

    @Override
    public void showNoteDetailUi(String noteId) {
        Intent i = new Intent(getActivity(),EditActivity.class);
        i.putExtra(EditActivity.EXTRA_NOTE_ID,noteId);
        startActivity(i);
    }

这两个方法一个表示创建便笺,一个表示编辑便笺,他们最终都是打开了EditActivity,只不过后者多传递了一个id的参数,这个id是一个便笺NoteBean的唯一标识。
所以EditActivity里获取的noteId是可以为空的,最后可以看到我们把他作为参数传到了EditPresenter 的构造方法里。

接下来创建EditFragment,照旧,先完成它的初始布局。这次直接把布局xml和代码一起贴上:

layout/edit_frag:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#F2F2F2"
    android:orientation="vertical">

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        app:cardBackgroundColor="#FFFFFF"
        app:cardCornerRadius="2dp"
        app:cardElevation="4dp">

        <EditText
            android:id="@+id/edit_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:hint="请输入标题"
            android:minHeight="40dp"
            android:padding="4dp"
            android:textSize="22sp"
            android:background="@null"
            />
    </android.support.v7.widget.CardView>

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp"
        app:cardBackgroundColor="#FFFFFF"
        app:cardCornerRadius="2dp"
        app:cardElevation="4dp">

        <EditText
            android:id="@+id/edit_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="top"
            android:hint="请输入内容"
            android:padding="4dp"
            android:textSize="18sp"
            android:background="@null" />
    </android.support.v7.widget.CardView>

</LinearLayout>

EditFragment:

public class EditFragment extends Fragment {


    private EditText mTitle;
    private EditText mContent;
    private FloatingActionButton fab;


    public static EditFragment getInstence(){
        return  new EditFragment();
    }


    @Override
    public void onResume() {
        super.onResume();
        //todo:初始化
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.edit_frag,container,false);
        mTitle = (EditText) v.findViewById(R.id.edit_title);
        mContent = (EditText) v.findViewById(R.id.edit_content);
        fab = (FloatingActionButton) getActivity().findViewById(R.id.fab);

        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            //todo:保存便笺
            }
        });

        setHasOptionsMenu(true);

        return v;
    }


    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.edit_menu,menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()){
            case R.id.delete_note:
                //todo:删除便笺
                break;
        }
        return true;
    }

menu/edit_menu:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/delete_note"
        android:title="删除"
        app:showAsAction="always|withText"/>
</menu>

哇,贼简单,预留的//todo也才3个。相信你过一遍就懂了,那么接下来我们要干什么?
接下来该给他设计V接口啦,创建EditContract类:

public class EditContract {

    interface View extends BaseView<Presenter>{
    }

    interface Presenter extends BasePresenter{
    }
}

一样的,我们现在考虑考虑编辑界面有哪些跟显示有关的逻辑。
考虑好了,发现跟显示有关的并不多,可以拟出如下接口:

public class EditContract {

    interface View extends BaseView<Presenter>{

        void showNoteList(); //显示便笺列表(即返回主界面)

        void setTitle(String title);

        void setContent(String content);

        void showError();

        void showEmptyError();

        boolean isActive();

    }

    interface Presenter extends BasePresenter{

    }
}

注意一样要有boolean isActive(); 它还是比较有用的。

接下来就让EditFragment成为这个V接口的实现类吧。具体代码如下:

EditFragment:

public class EditFragment extends Fragment implements EditContract.View {

    private EditContract.Presenter presenter; //View持有Presenter

    private EditText mTitle;
    private EditText mContent;
    private FloatingActionButton fab;

    //……………………省略已有代码

    //以下为EditContract.View接口实现


    @Override
    public void setPresenter(EditContract.Presenter presenter) {
        this.presenter = presenter;
    }

    @Override
    public void showNoteList() {
        getActivity().finish();
    }

    public void setTitle(String title) {
        mTitle.setText(title+"");
    }

    @Override
    public void setContent(String content) {
        mContent.setText(content+"");
    }

    @Override
    public void showError() {
        Snackbar.make(getView(),"加载失败",Snackbar.LENGTH_LONG).show();
    }

    @Override
    public void showEmptyError() {
        Snackbar.make(getView(),"标题和内容不能全空",Snackbar.LENGTH_LONG).show();
    }

    @Override
    public boolean isActive() {
        return isAdded();
    }
}

还是简单的。我们的V可以说是基本搞好了。下面设计P接口。
一样的,首先观察下EditFragment里留下来的//todo:

  • //todo:初始化
  • //todo:保存便笺
  • //todo:删除便笺

就这么3个,由此我们的P接口也就很好写了:

public class EditContract {

    interface View extends BaseView<Presenter>{
        //…………省略
    }

    interface Presenter extends BasePresenter{

        void loadNote();

        void saveNote(String title,String content);

        void deleteNote();
    }
}

接下来让EditFragment持有这个接口,把对应的方法放到对应的//todo里,代码如下:

public class EditFragment extends Fragment implements EditContract.View {

    private EditContract.Presenter presenter; //View持有Presenter

    private EditText mTitle;
    private EditText mContent;
    private FloatingActionButton fab;


    public static EditFragment getInstence(){
        return  new EditFragment();
    }

    @Override
    public void onResume() {
        super.onResume();
        presenter.start();
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.edit_frag,container,false);
        mTitle = (EditText) v.findViewById(R.id.edit_title);
        mContent = (EditText) v.findViewById(R.id.edit_content);
        fab = (FloatingActionButton) getActivity().findViewById(R.id.fab);

        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                presenter.saveNote(mTitle.getText().toString(),mContent.getText().toString());
            }
        });

        setHasOptionsMenu(true);

        return v;
    }


    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.edit_menu,menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()){
            case R.id.delete_note:
                presenter.deleteNote();
                break;
        }
        return true;
    }

    //以下为EditContract.View接口实现


    @Override
    public void setPresenter(EditContract.Presenter presenter) {
        this.presenter = presenter;
    }
    //………………省略其他剩余的接口方法
}

接下来创建P的实现类EditPresenter。先贴上完整代码:

public class EditPresenter implements EditContract.Presenter {

    private EditContract.View editView; //Presenter持有View
    private NotesRepository notesRepository; //MVP的Model,管理数据处理
    private String loadNoteId;
    private boolean isNewNote;


    public EditPresenter(NotesRepository notesRepository, EditContract.View editView, @Nullable String noteId){
        this.notesRepository = notesRepository;
        this.editView = editView;
        this.loadNoteId = noteId;
        if(TextUtils.isEmpty(loadNoteId)){
           isNewNote = true;
        }
        this.editView.setPresenter(this);//重要!别落了
    }



    //以下为EditContract.Presenter接口实现
    @Override
    public void start() {
        loadNote();
    }

    @Override
    public void loadNote() {
        if(isNewNote){
            return;  //新建的便笺不用加载
        }
        notesRepository.getNote(loadNoteId, new NoteDataSource.LoadNoteCallback() {
            @Override
            public void loadSuccess(NoteBean note) {

                if(editView.isActive()){
                    editView.setTitle(note.title);
                    editView.setContent(note.content);
                }
            }

            @Override
            public void loadFailed() {
                if(editView.isActive()){
                    editView.showError();
                }
            }
        });
    }

    @Override
    public void saveNote(String title, String content) {
        if(TextUtils.isEmpty(title) && TextUtils.isEmpty(content)){
            editView.showEmptyError();
            return;
        }

        if(isNewNote){
            createNote(title,content);
        }else{
            updateNote(loadNoteId,title,content);
        }
    }

    private void updateNote(String loadNoteId, String title, String content) {
        NoteBean bean = new NoteBean(loadNoteId,title,content,true);
        notesRepository.updateNote(bean);
        editView.showNoteList();
    }

    private void createNote(String title, String content) {
        NoteBean bean = new NoteBean(title,content,true);
        notesRepository.saveNote(bean);
        editView.showNoteList();
    }

    @Override
    public void deleteNote() {
        if(isNewNote){
            editView.showNoteList(); //如果是新建的便笺,直接返回主界面即可
        }else{
            notesRepository.deleteNote(loadNoteId);
            editView.showNoteList();
        }
    }

}

可以看到,EditPresenter的构造方法除了要传入M和V以外,还需要传入一个String noteId ,然后我们通过判断它是否为空来确定是创建新便笺还是编辑已存在的便笺。在public void loadNote()方法里,如果是创建新编辑的话,那没什么可加载的,直接return掉,否则就根据noteId去调用notesRepository.getNote来获取这个便笺的数据,并在回调里做对应的处理。保存便笺时,即调用public void saveNote(String title, String content)时,也会根据情况来选择创建一个便笺还是更新已存在的便笺。

那么恭喜,编辑界面的V和P也已经设计好了。现在我们只用关心数据M怎么写了,其他的都不用管了~

Model的编写

上一篇文章的开头就已经设计好了Model的接口,并使用的NotesRepository作为具体实现类。我们接下来呢,打算完成一个数据库+缓存的M。

数据库Model的编写:

首先我们先写好数据库的M,这个数据库呢我们就不依赖什么开源库了,直接使用系统的SQLiteOpenHelper,初学者们肯定都学过~正好可以练习练习是吧。
我们先创建一个NoteDbHelper类,继承于SQLiteOpenHelper,然后在里面设计好我们的表。
代码如下:

**
 * Created by ccy on 2017-07-13.
 * 数据库创建类
 */

public class NoteDbHelper extends SQLiteOpenHelper {

    private static final int VERSION = 1;   //版本号
    private static final String DATABASE_NAME = "Notes.db"; //数据库名称

    public static final String TABLE_NAME = "notes";
    public static final String COLUMN_ID = "entryid";
    public static final String COLUMN_TITLE = "title";
    public static final String COLUMN_CONTENT = "content";
    public static final String COLUMN_IS_ACTIVE = "active";

    private static final String SQL_CREATE_ENTRIES =   //建表,该有的空格别忘敲
            "CREATE TABLE "+TABLE_NAME+"("+
                    COLUMN_ID+" TEXT PRIMARY KEY,"+
                    COLUMN_TITLE+" TEXT,"+
                    COLUMN_CONTENT+" TEXT,"+
                    COLUMN_IS_ACTIVE+" INTEGER"+
                    ")";



    public NoteDbHelper(Context context) {
        super(context, DATABASE_NAME , null, VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(SQL_CREATE_ENTRIES);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

easy,一共4列,跟NoteBean里的属性是一 一对应的,并将COLUMN_ID作为了主键。

然后,创建NotesLocalDataSource类,实现之前设计好的接口NoteDataSource。然后去实现这个接口里的所有方法。那么接下来就要看大家SQLite的知识掌握的怎么样啦:

/**
 * Created by ccy on 2017-07-13.
 * 数据库操作类 ( Model 实现类)
 * 单例
 */

public class NotesLocalDataSource implements NoteDataSource {

    private static NotesLocalDataSource INSTANCE = null;
    private NoteDbHelper dbHelper;


    private NotesLocalDataSource(Context context) {
        dbHelper = new NoteDbHelper(context);
    }

    public static NotesLocalDataSource getInstance(Context context) {
        if (INSTANCE == null) {
            INSTANCE = new NotesLocalDataSource(context);
        }
        return INSTANCE;
    }


    @Override
    public void getNote(String noteId, LoadNoteCallback callback) {
        NoteBean note = null;
        SQLiteDatabase db = dbHelper.getReadableDatabase();

        String queryColums[] = new String[]{
                NoteDbHelper.COLUMN_ID,
                NoteDbHelper.COLUMN_TITLE,
                NoteDbHelper.COLUMN_CONTENT,
                NoteDbHelper.COLUMN_IS_ACTIVE
        };
        String selection = NoteDbHelper.COLUMN_ID + " LIKE ?";
        String[] selectionArgs = {noteId};

        Cursor cursor = null;
        try {
            cursor = db.query(NoteDbHelper.TABLE_NAME, queryColums, selection, selectionArgs, null, null, null);
            if (cursor != null && cursor.getCount() > 0) {
                cursor.moveToFirst();
                String id = cursor.getString(cursor.getColumnIndex(NoteDbHelper.COLUMN_ID));
                String title = cursor.getString(cursor.getColumnIndex(NoteDbHelper.COLUMN_TITLE));
                String content = cursor.getString(cursor.getColumnIndex(NoteDbHelper.COLUMN_CONTENT));
                boolean isActive = cursor.getInt(cursor.getColumnIndex(NoteDbHelper.COLUMN_IS_ACTIVE)) == 1;
                note = new NoteBean(id, title, content, isActive);
            }
        } catch (Exception e) {
            callback.loadFailed();
        } finally {
            if (cursor != null) {
                cursor.close();
            }
            db.close();
        }
        if(note == null){
            callback.loadFailed();
        }else {
            callback.loadSuccess(note);
        }

    }

    @Override
    public void getNotes(LoadNotesCallback callback) {
        List<NoteBean> notes = new ArrayList<>();
        SQLiteDatabase db = dbHelper.getReadableDatabase();

        String queryColums[] = {
                NoteDbHelper.COLUMN_ID,
                NoteDbHelper.COLUMN_TITLE,
                NoteDbHelper.COLUMN_CONTENT,
                NoteDbHelper.COLUMN_IS_ACTIVE
        };

        Cursor cursor = null;
        try {
            cursor = db.query(NoteDbHelper.TABLE_NAME, queryColums, null, null, null, null, null);
            if (cursor != null && cursor.getCount() > 0) {
                while (cursor.moveToNext()) {
                    String id = cursor.getString(cursor.getColumnIndex(NoteDbHelper.COLUMN_ID));
                    String title = cursor.getString(cursor.getColumnIndex(NoteDbHelper.COLUMN_TITLE));
                    String content = cursor.getString(cursor.getColumnIndex(NoteDbHelper.COLUMN_CONTENT));
                    boolean isActive = cursor.getInt(cursor.getColumnIndex(NoteDbHelper.COLUMN_IS_ACTIVE)) == 1;
                    NoteBean bean = new NoteBean(id, title, content, isActive);
                    notes.add(bean);
                }
            }
        } catch (Exception e) {
            callback.loadFailed();
        } finally {
            if (cursor != null) {
                cursor.close();
            }
            db.close();
        }

        callback.loadSucess(notes);

    }

    @Override
    public void saveNote(NoteBean note) {
        if(note == null){
            return;
        }
        SQLiteDatabase db = dbHelper.getWritableDatabase();

        ContentValues values = new ContentValues();
        values.put(NoteDbHelper.COLUMN_ID,note.id);
        values.put(NoteDbHelper.COLUMN_TITLE,note.title);
        values.put(NoteDbHelper.COLUMN_CONTENT,note.content);
        values.put(NoteDbHelper.COLUMN_IS_ACTIVE,note.isActive);

        db.insert(NoteDbHelper.TABLE_NAME,null,values);
        db.close();
    }

    @Override
    public void updateNote(NoteBean note) {
        if(note == null){
            return;
        }
        SQLiteDatabase db = dbHelper.getWritableDatabase();

        ContentValues values = new ContentValues();
        values.put(NoteDbHelper.COLUMN_TITLE,note.title);
        values.put(NoteDbHelper.COLUMN_CONTENT,note.content);
        values.put(NoteDbHelper.COLUMN_IS_ACTIVE,note.isActive);

        String selection = NoteDbHelper.COLUMN_ID+" LIKE ?";
        String[] selectionArgs = {note.id};

        db.update(NoteDbHelper.TABLE_NAME,values,selection,selectionArgs);

        db.close();
    }

    @Override
    public void markNote(NoteBean note, boolean isActive) {
        if(note == null){
            return;
        }
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(NoteDbHelper.COLUMN_IS_ACTIVE,isActive);

        String selection = NoteDbHelper.COLUMN_ID+ " LIKE ?";
        String[] selectionArgs = {note.id};

        db.update(NoteDbHelper.TABLE_NAME,values,selection,selectionArgs);

        db.close();
    }

    @Override
    public void clearCompleteNotes() {
        SQLiteDatabase db = dbHelper.getWritableDatabase();

        String selection = NoteDbHelper.COLUMN_IS_ACTIVE+" LIKE ?";
        String[] selectionArgs ={"0"};   //0即false

        db.delete(NoteDbHelper.TABLE_NAME,selection,selectionArgs);
        db.close();
    }

    @Override
    public void deleteAllNotes() {
        SQLiteDatabase db = dbHelper.getWritableDatabase();

        db.delete(NoteDbHelper.TABLE_NAME,null,null);
        db.close();
    }

    @Override
    public void deleteNote(String noteId) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();

        String selection = NoteDbHelper.COLUMN_ID + " LIKE ?";
        String[] selectionArgs = {noteId};

        db.delete(NoteDbHelper.TABLE_NAME,selection,selectionArgs);
        db.close();
    }

    @Override
    public void cacheEnable(boolean enable) {
        //无操作。
    }
}

上面这些呢都是SQLite的知识,我也没什么可说的,还望应该都已经掌握。不过我们可以看到public void cacheEnable(boolean enable)是空的。是的,这个M只负责以数据库作为数据源,并没有缓存的概念,所以他的这个方法是空的。
那么以数据库的M已经写好了,其实这个时候,我们已经可以把它作为最终M设置给其他界面了,只不过他没有缓存概念(也就是说MainPresenter里loadNotes(boolean forceUpdate,boolean showLoadingUI);这个方法的第一个参数没有了意义,但不影响使用)。

数据库+缓存的Model编写

我们目的是要去实现数据库+缓存的M,所以,我们接下来开始修改NotesRepository,让他持有我们上面写好的NotesLocalDataSource类,并使用一个Map < String,NoteBean > 作为缓存的角色。完整的代码如下所示:

public class NotesRepository implements NoteDataSource {

    private static NotesRepository INSTANCE = null;
    private Map<String,NoteBean> notesCache; //数据缓存,key为id
    private boolean cacheEnable = false; //是否可以从缓存中获取数据

    private NoteDataSource notesLocalDataSource;//从数据库获取数据的Model

    private NotesRepository(NoteDataSource notesLocalDataSource){
        this.notesLocalDataSource = notesLocalDataSource;
    }

    public static NotesRepository getInstence(NoteDataSource notesLocalDataSource){
        if(INSTANCE == null){
            INSTANCE = new NotesRepository(notesLocalDataSource);
        }
        return INSTANCE;
    }

    @Override
    public void getNote(final String noteId, final LoadNoteCallback callback) {

        if(notesCache != null && cacheEnable){  //直接从缓存中取
            NoteBean bean =getNoteFromCacheById(noteId);
            if(bean != null){
                callback.loadSuccess(notesCache.get(noteId));
                return;
            }
        }
        notesLocalDataSource.getNote(noteId, new LoadNoteCallback() {
            @Override
            public void loadSuccess(NoteBean note) {
               if(notesCache == null){
                   notesCache = new LinkedHashMap<String, NoteBean>();
               }
               notesCache.put(noteId,note);
                callback.loadSuccess(note);
            }

            @Override
            public void loadFailed() {
                callback.loadFailed();
            }
        });

    }

    /**
     * 尝试从缓存中获取note
     * @param noteId
     * @return
     */
    private NoteBean getNoteFromCacheById(String noteId){
        if(notesCache == null || notesCache.isEmpty()){
            return null;
        }else{
            return notesCache.get(noteId);
        }
    }

    @Override
    public void getNotes(final LoadNotesCallback callback) {

        if(notesCache != null && cacheEnable){
            callback.loadSucess(new ArrayList<NoteBean>(notesCache.values()));
            return;
        }

        notesLocalDataSource.getNotes(new LoadNotesCallback() {
            @Override
            public void loadSucess(List<NoteBean> notes) {
                refreshCache(notes);
                callback.loadSucess(notes);
            }

            @Override
            public void loadFailed() {
                callback.loadFailed();
            }
        });
    }

    private void refreshCache(List<NoteBean> notes) {
        if(notesCache == null){
            notesCache = new LinkedHashMap<>();
        }
        notesCache.clear();
        for (NoteBean bean : notes){
            notesCache.put(bean.id,bean);
        }
        cacheEnable = true;
    }

    @Override
    public void saveNote(NoteBean note) {
        notesLocalDataSource.saveNote(note);
        if(notesCache == null){
            notesCache = new LinkedHashMap<>();
        }
        notesCache.put(note.id,note);
    }

    @Override
    public void updateNote(NoteBean note) {
        notesLocalDataSource.updateNote(note);
        if(notesCache == null){
            notesCache = new LinkedHashMap<>();
        }
        notesCache.put(note.id,note);
    }

    @Override
    public void markNote(NoteBean note, boolean isActive) {
        notesLocalDataSource.markNote(note,isActive);
        if(notesCache == null){
            notesCache = new LinkedHashMap<>();
        }
        NoteBean newBean = new NoteBean(note.id,note.title,note.content,isActive);
        notesCache.put(note.id,newBean);
    }

    @Override
    public void clearCompleteNotes() {
        notesLocalDataSource.clearCompleteNotes();
        if(notesCache == null){
            notesCache = new LinkedHashMap<>();
        }
        Iterator<Map.Entry<String, NoteBean>> iterator = notesCache.entrySet().iterator();
        while(iterator.hasNext()){
            Map.Entry<String, NoteBean> next = iterator.next();
            if(!next.getValue().isActive){
                iterator.remove();
            }
        }

    }

    @Override
    public void deleteAllNotes() {
        notesLocalDataSource.deleteAllNotes();
        if(notesCache == null){
            notesCache = new LinkedHashMap<>();
        }
        notesCache.clear();
    }

    @Override
    public void deleteNote(String noteId) {
        notesLocalDataSource.deleteNote(noteId);
        if(notesCache == null){
            notesCache = new LinkedHashMap<>();
        }
        notesCache.remove(noteId);
    }

    @Override
    public void cacheEnable(boolean enable) {
        this.cacheEnable = enable;
    }
}

在他的每个接口方法里,我们都会去调用NotesLocalDataSource对应的方法,同时也实现了缓存的对应操作。在public void getNotepublic void getNotes方法里,我们先判断缓存是否可用的标志位“cacheEnable ”,如果是true,那么直接从缓存Map里取数据,如果是false,则调用持有的NotesLocalDataSource的对应方法。

好啦,这样数据库+缓存的M就写好啦,也代表着我们的项目已经完成了~~
你有得到收获吗?~如果现在让你去实现一个数据库+缓存+服务器的M你会写吗?我本来也是想打算再用bmob写个服务器的M的,不过这已经不算是我们MVP模式的事了~就没写啦。

MVP模式优点还是很明显哒,不过我们这项目太小了,也许大家看完只觉得代码多了许多,还麻烦了许多。。。不过等大家以后体会了当界面逻辑和业务逻辑像八宝粥一样混合在一起所带来的痛苦后,,,就知道mvp的好了~

其他补充

在给Presenter构造方法里传递Model时,我们使用的是如下方法:
Injection.provideRespository(this)
他的具体实现是这样的:

/**
 * Created by ccy on 2017-07-13.
 * 提供NotesRespository
 */

public class Injection {
    public static NotesRepository provideRespository(Context context){
        return NotesRepository.getInstence(NotesLocalDataSource.getInstance(context));
    }
}

这样写的好处是方便统一管理,我们现在的NotesRespository是数据库+缓存,但是哪天来个新需求说要实现数据库+缓存+服务器的话,你就要重新写一个NotesRespository并替换之前的,这个时候你只用修改Injection里的provideRespository即可,而不用去找每个Activity一个一个的去替换。

在Activity里创建Fragment时,我们用的是如下工具方法:
ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),mainFragment,R.id.fragment_content);
它的具体实现:

public class ActivityUtils {

    /**
     * 将fragment放置至指定布局id
     * @param fragmentManager
     * @param fragment
     * @param frameId 布局id
     */
    public static void addFragmentToActivity (@NonNull FragmentManager fragmentManager,
                                              @NonNull Fragment fragment, int frameId) {
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.add(frameId, fragment);
        transaction.commit();
    }

}

源码地址https://github.com/CCY0122/MVP-Note_app

猜你喜欢

转载自blog.csdn.net/ccy0122/article/details/75209609