Project address: github.com/razerdp/Fri…

The last link: http://www.jianshu.com/p/deda1849a084

The next link: http://www.jianshu.com/p/8d24f9b7a63a


Halfway through our project, I decided to stop and refactor because of some problems.

Yes, as a developer I think refactoring is a normal thing to do. Fortunately, since the abstraction of our ViewHolder, the reconfiguration project is not very big, perhaps we can regard it as a partial reconfiguration.

The reconstruction contents are as follows:

  • Comment on improvements to controls
  • Overwork the relationship between viewholder and activity (in MVP mode)

This article will explain why and how to refactor.


The reason:

The reason for this is simple, because as the code goes along, we discover that the more we want loose coupling, the more we find it tight.

Remember this picture from the last post?

It looks like it’s ok, and yes, it’s ok until you review it, but when you review it, it’s not ok.

The reason is simple, because our controller is one-way.

The viewholder operates the controller one way, and the controller notifies the Activity to update the adapter.

Before that, our actions were almost one-way, like “like.”

The operation of “like” is: “like” → request → request completion → refresh data → show → finish.

That’s fine. We don’t get involved, we don’t interfere, we do our jobs.

But now we need to comment, and here comes the question:

  • The comment control cannot be in the ViewHolder, it must be in the Activity layer
  • Actions such as commenting are triggered in the ViewHolder, such as clicking on a comment to reply.

The problem is with the one-way connection, and it’s clear that what we need now is for the ViewHolder to call one of the Activity’s methods to control the comment box. If you go with this structure, all you get is that these classes get more and more closely related.

So, we need to take a different approach.

So you have this refactoring.

Refactoring:

In terms of patterns, the big MVC is finding the Model layer too heavy to handle with event triggers and data, among the increasing demands.

There are plenty of examples and explanations on the Web, but I won’t go into them here.

And, of course, MVVM, which has become popular because of DataBinding, is probably going to be very rare in the future when you call a View in Java code and stuff it with data.

But at least not right now, so to prevent havoc, we’re going with MVP mode.


First, we need to kill the old one, pick up the machete in hand (del key), go to the app of our project and cut off the package of Controller.

Then, we bring in the new favorite MVP bag -V- (old out, new out ←_←)

So our structure looks like this (rearranged) :

Other changes are not changed, the app package is now as follows:

  • App packages mainly store various base classes or configurations, interfaces, etc
    • Adapter: This is home to the Adapter base class of our circle of friends
    • Config: various configuration parameters, such as the definition of the drop-down refresh mode, whether to like the status of the definition and so on.
    • HTTPS: Volley encapsulation, HMM… We just switched from network to HTTPS, which I actually think should be HTTP, because HTTP and HTTPS are two different things, right
    • -Serena: Well, there are all these interfaces
    • MVP: The star of the show

Everything else, we’ll focus on the MVP.

According to the specification, we have a model/ Presenter/View package, then m layer to put the bean package and implementation class package, and root package is the interface.

The rest of the same

Step 1: View


First we define an interface called DynamicView that implements three methods:

  • Refresh likes data
  • Refreshing comment data
  • Show the comment box
/** * Created by big light on 2016/3/17. * MVP - View layer * only used to update/display data and partial user interaction */
public interface DynamicView{

    // Like refresh
    void refreshPraiseData(int currentDynamicPos,
                           @CommonValue.PraiseState int praiseState, @NonNull List<UserInfo> praiseList);

    // Refresh the comments
    void refreshCommentData(int currentDynamicPos,
                            @RequestType.CommentRequestType int requestType, @NonNull List<CommentInfo> commentList);

    // Comment box display
    void showInputBox(int currentDynamicPos, @CommonValue.CommentType int commentType, CommentInfo commentInfo);
}

Copy the code

In the last article, I mentioned that exposing MomentsInfo is a bad solution, so this time we just need to throw out the position of the viewholder of the current operation, and the corresponding data can be obtained from the adapter outside.

The first two methods of our View implementation are just for refreshing data from our beloved Model, of course, and the third method is the part of the interaction that the View layer is supposed to do.

So once we’ve done the View, let’s do the Model.

Step 2: Model


For models, we can simply choose a class, but for more complex functions, we should still use interfaces to loosen coupling.

That what, more composition less inheritance, interface high abstraction. This is what a good project should have, right (of course, my project is not a good project -V-, but we can try to be good)

Without further ado, first create an interface for likes:

/** * Created by large bulb on 2016/3/17. * Model - Upvote interface */
public interface PraiseModel {

    / / thumb up
    void addPraise(int currentDynamicPos, long userid, long dynamicid);

    // Unlike
    void cancelPraise(int currentDynamicPos, long userid, long dynamicid);
}
Copy the code

The parameters of the like interface are not very different from our previous controller.

Then create our implementation class: DynamicModelImpl

/** * Created by big light on 2016/3/17. * MVP-model * complex logic operation/request/time etc., processed data provides ** If too complex operation, may need interface to loose coupling */
public class DynamicModelImpl implements BaseResponseListener.PraiseModel {
    private DynamicResultCallBack callBack;

    public DynamicModelImpl(DynamicResultCallBack callBack) {
        this.callBack = callBack;
    }

    //=============================================================request
    private DynamicAddPraiseRequest mDynamicAddPraiseRequest;
    private DynamicCancelPraiseRequest mDynamicCancelPraiseRequest;

    //=============================================================public methods
    @Override
    public void addPraise(int currentDynamicPos, long userid, long dynamicid) {}@Override
    public void cancelPraise(int currentDynamicPos, long userid, long dynamicid) {}//=============================================================request methods
    @Override
    public void onStart(BaseResponse response) {}@Override
    public void onStop(BaseResponse response) {}@Override
    public void onFailure(BaseResponse response) {}@Override
    public void onSuccess(BaseResponse response) {}}Copy the code

Our implementation class basically needs to implement one interface: BaseResponseListener

Because requests are executed here, we designed Volley to be abstracted separately so that any class could use it.

Implementing our Model interface at the same time gives us the structure of the code.

Finally, we need a callback to inform the presenter that the job is done and that the presenter needs to report to the commander in chief for the next stage of the assignment.

Step 3: Presenter


The main function of a Presenter is to communicate with layer M and layer V, so our Presenter needs to hold layer V and layer M, but the communication between layer P and layer V is mainly through the interface.

So our Presenter says:

/** * Created by big light on 2016/3/17. * MVP - Bootstrap layer * used to receive view operation commands to distribute to model layer implementation */
public class DynamicPresenterImpl implements DynamicResultCallBack {
    private DynamicModelImpl mModel;
    private DynamicView mView;

    public DynamicPresenterImpl(DynamicView view) {
        mView = view;
        mModel = new DynamicModelImpl(this);
    }

    / / thumb up
    public void addPraise(int curDynamicPos, long dynamicId) {
        mModel.addPraise(curDynamicPos, LocalHostInfo.INSTANCE.getHostId(), dynamicId);
    }

    / / cancel the praise
    public void cancelPraise(int curDynamicPos, long dynamicId) {
        mModel.cancelPraise(curDynamicPos, LocalHostInfo.INSTANCE.getHostId(), dynamicId);
    }

    @Override
    public void onResultCallBack(BaseResponse response) {}}Copy the code

DynamicResultCallBack (BaseResponse Response) {} is a callback to a request that has been successfully processed by layer M.

At this point, our MVP structure is roughly written. The next thing you need to do is to transform your Activity

Step 4: Modify your Activity (View)


Back to our FriendCircleDemoActivity, first we implement DynamicView and give presenter to new in onCreate:

/** * Created by large bulb on 2016/2/25. * friend circle Demo window */
public class FriendCircleDemoActivity extends FriendCircleBaseActivity implements DynamicView.View.OnClickListener {
    private FriendCircleRequest mCircleRequest;
    privateDynamicPresenterImpl mPresenter; .@Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mPresenter = new DynamicPresenterImpl(this);
        initView();
        initReq();
        //mListView.manualRefresh();}...//=============================================================mvp - view's method

    @Override
    public void refreshPraiseData(int currentDynamicPos,
                                  @CommonValue.PraiseState int praiseState, @NonNull List<UserInfo> praiseList) {}@Override
    public void refreshCommentData(int currentDynamicPos,
                                   @RequestType.CommentRequestType int requestType,
                                   @NonNull List<CommentInfo> commentList) {}@Override
    public void showInputBox(int currentDynamicPos, @CommonValue.CommentType int commentType, CommentInfo commentInfo) {}}Copy the code

In fact, in order to facilitate life control, our presenter would theoretically need to write a corresponding life span method, but let’s not do that here, after all, it is only an Activity to play around with.

Our View implements the three methods of DynamicView, which will be updated by Presenter after the data request has been completed.

We can actually complete our code right now, like “like” :

    @Override
    public void refreshPraiseData(int currentDynamicPos,
                                  @CommonValue.PraiseState int praiseState, @NonNull List<UserInfo> praiseList) {
        MomentsInfo info = mAdapter.getItem(currentDynamicPos);
        if(info ! =null) {
            info.dynamicInfo.praiseState = praiseState;
            if(info.praiseList ! =null) {
                info.praiseList.clear();
                info.praiseList.addAll(praiseList);
            }
            else {
                info.praiseList = praiseList;
            }
        }
        mAdapter.notifyDataSetChanged();
    }
Copy the code

Compared to the previous code, this code is much cleaner, now looking back at the historical code, I can’t even look at TAT…

Similarly, other implementations can be written in advance. I’m going to skip it here.

Step 5: Complete the code


First of all, we will complete the code of Model. In fact, the code of Model is not very different from that of controller. Here we show the code of “like” and “unlike”.

   @Override
    public void addPraise(int currentDynamicPos, long userid, long dynamicid) {
        if (mDynamicAddPraiseRequest == null) {
            mDynamicAddPraiseRequest = new DynamicAddPraiseRequest();
            mDynamicAddPraiseRequest.setOnResponseListener(this);
            mDynamicAddPraiseRequest.setRequestType(RequestType.ADD_PRAISE);
        }
        mDynamicAddPraiseRequest.setCurrentDynamicPos(currentDynamicPos);
        mDynamicAddPraiseRequest.userid = userid;
        mDynamicAddPraiseRequest.dynamicid = dynamicid;
        mDynamicAddPraiseRequest.execute();
    }
Copy the code

With request we need to pass in the current dynamic location and then set the value to BaseResponse and call back to Presenter when done.

Next, complete the Presenter code:

Presenters are relatively easy because all the complexity is done in the Model, and presenters simply distribute:

We’ll focus on callback:

@Override
    public void onResultCallBack(BaseResponse response) {
        if(mView ! =null) {
            final int curDynamicPos = (int) response.getData();
            switch (response.getRequestType()) {
                case RequestType.ADD_PRAISE:
                    List<UserInfo> praiseList = (List<UserInfo>) response.getDatas();
                    mView.refreshPraiseData(curDynamicPos, CommonValue.HAS_PRAISE, praiseList);
                    break;
                case RequestType.CANCEL_PRAISE:
                    List<UserInfo> praiseList2 = (List<UserInfo>) response.getDatas();
                    mView.refreshPraiseData(curDynamicPos, CommonValue.NOT_PRAISE, praiseList2);
                    break; }}}Copy the code

Callback will notify the view to perform different refresh operations for different request types. We have already written the view code above.

Finally


This is my first time to use the MVP, looking at the tutorial on the masturbation, also do not know whether to write, but feel the MVP structure really look very comfortable, clear responsibilities, very cool maintenance.

Oh, forget, there is a second point about refactoring, the refactoring of comment controls…

Here are two pieces of code to illustrate:

This is the previous code

 public void setCommentText(CommentInfo info) {
        if (info == null) return;
        boolean hasContent = false;
        // Determine if the content is consistent according to hashCode
        if (key == 0) {
            key = info.hashCode();
        }
        else {
            hasContent = (key == info.hashCode());
        }
        if(! hasContent) { key = info.hashCode(); setText("");
            setTag(info);
            createCommentStringBuilder(info);
        }
        else {
            try {
                setText(mSpannableStringBuilderAllVer);
            } catch (NullPointerException e) {
                e.printStackTrace();
                Log.e(TAG, "I don't think that's going to happen, but I'd better capture it in case I get hit in the face..."); }}}Copy the code

This is the current code.

    public void setCommentText(CommentInfo info) {
        if (info == null) return;
        try {
            setTag(info);
            createCommentStringBuilder(info);
        } catch (NullPointerException e) {
            e.printStackTrace();
            Log.e(TAG, "I don't think that's going to happen, but I'd better capture it in case I get hit in the face..."); }}private void createCommentStringBuilder(@NonNull CommentInfo info) {
        if (mSpannableStringBuilderAllVer == null) {
            mSpannableStringBuilderAllVer = new SpannableStringBuilderAllVer();
        }
        else{ mSpannableStringBuilderAllVer.clear(); mSpannableStringBuilderAllVer.clearSpans(); }... }Copy the code

Both the difference between different mSpannableStringBuilderAllVer this object.

Here’s what I thought before:

If the update two commentInfo object consistent, mSpannableStringBuilderAllVer exist at the same time, are used directly, no need to update

But because we’ve optimized commentWidgets, that is, removeView commentwidgets to pool, click on me

As a result, the info may be the same, but the comments that are supposed to belong to this dynamic appear in another dynamic, because the objects in the pool refer to the same one.

So this time we’ll have to redo the text assignment anyway, and the problem is solved.

In the next post, we finish the comment function.

Ps: JUST checked the database, there is a dynamic is 50 comments oh… For the construction of this fake data, every day in the fine points of the scene… It won’t be so painful once the comments feature is fixed… Although every comment is made by the user “Yuyijun” ←_←