Implementation of universal encapsulation of Android MVP basefragment

Time:2020-11-5

This is the sixth article in our series on the basic MVP framework. Basemvp has been encapsulated by us almost. From the last article (Android MVP architecture (V) MVP multiple presenter dependency injection), we have solved the problem of multiple presenters. This is to dynamically instantiate different presenters by means of dependency injection and reflection The layer implements the class, and binds and unbinds the same view. This is a solution for a view to manually update multiple presenters.

To build the basic MVP framework, there is still something missing. We only encapsulated the baseactivity class of a base class. This class is provided to the activity to inherit. However, in our actual project, it is inevitable that fragments will appear. Therefore, today, we will take you to encapsulate a basefragment base class to improve the basic framework as much as possible.

To encapsulate the basefragment base class, it is not difficult to refer to the encapsulation of baseactivity, because the lifecycles of activity and fragment are very similar, and fragment is based on activity, so to put it bluntly, there must be some deviation in the code. Compared with previous versions, this time I added a basefragment base class to the package and added several classes to test it.

Next, let’s take a look at the basefragment base class, and directly add the code:

 

Create a new basefragment base class:

package com.test.mvp.mvpdemo.mvp.v6.basemvp;
 
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.test.mvp.mvpdemo.mvp.v6.inject.InjectPresenter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
 
public abstract class BaseFragment extends Fragment implements IBaseView {
 
 private List<BasePresenter> mInjectPresenters;
 
 private View mLayoutView;
 
 protected abstract @LayoutRes int setLayout();
 
 protected abstract void initViews(@Nullable Bundle savedInstanceState);
 
 protected abstract void initData();
 
 @SuppressWarnings("ConstantConditions")
 protected <T extends View> T $(@IdRes int viewId) {
  return this.getView().findViewById(viewId);
 }
 
 @SuppressWarnings({"unchecked", "TryWithIdenticalCatches"})
 @Nullable
 @Override
 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
  View view = inflater.inflate(setLayout(), container, false);
 
  mInjectPresenters = new ArrayList<>();
 
  //Get declared variables, including private variables
  Field[] fields = this.getClass().getDeclaredFields();
  for (Field field : fields) {
   //Gets the annotation type above the variable
   InjectPresenter injectPresenter = field.getAnnotation(InjectPresenter.class);
   if (injectPresenter != null) {
    try {
     Class<? extends BasePresenter> type = (Class<? extends BasePresenter>) field.getType();
     BasePresenter mInjectPresenter = type.newInstance();
     //Binding
     mInjectPresenter.attach(this);
     field.setAccessible(true);
     field.set(this, mInjectPresenter);
     mInjectPresenters.add(mInjectPresenter);
    } catch (IllegalAccessException e) {
     e.printStackTrace();
    } catch (java.lang.InstantiationException e) {
     e.printStackTrace();
    } catch (ClassCastException e) {
     e.printStackTrace();
     throw new RuntimeException("SubClass must extends Class:BasePresenter");
    }
   }
  }
  return view;
 }
 
 @Override
 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
  super.onViewCreated(view, savedInstanceState);
 
  initViews(savedInstanceState);
  initData();
 }
 
 @Override
 public void onDestroy() {
  super.onDestroy();
  for (BasePresenter presenter : mInjectPresenters) {
   presenter.detach();
  }
  mInjectPresenters.clear();
  mInjectPresenters = null;
 }
}

Since we used dependency injection in the last article, we removed the generic parameter of basefragment class. And baseactivity in this version, I also remove this generic parameter, as shown in the following figure:

After removal:

The baseactivity here seems clean and concise. Otherwise, I need to input a parameter every time. I feel tired to think about it. Well, our basefragment and baseactivity are almost the same. There is no more explanation here. You can go to the previous articles to explain the code.

After writing a basefragment base class, I can’t wait to test whether it can work or not. Here, I create a new secondactivity class to store a fragment in the new activity for testing. The code of secondactivity is not difficult. It is to store this secondfragment in it. By the way, the secondactivity here does not inherit our baseactivity class. This is a normal activity. Please pay special attention. The code is very simple, as follows:

Create a new secondactivity class:

public class SecondActivity extends AppCompatActivity {
 
 @Override
 protected void onCreate(@Nullable Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_second);
  /**
   *Open a fragment
   */
  getSupportFragmentManager().beginTransaction().replace(R.id.second_container, new SecondFragment()).commit();
 }
}

Layout of secondactivity: a FrameLayout is used to store secondfragment.


<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 tools:context=".mvc.MainActivity">
 
 <FrameLayout
  android:id="@+id/second_container"
  android:layout_width="match_parent"
  android:layout_height="match_parent" />
 
</android.support.constraint.ConstraintLayout>

The next step is the actual use of our basefragment class. We create a new implementation class of secondfragment, which inherits from the basefragment class. The secondfragment here is the view layer of MVP, and belongs to the view layer as our activity. Here, I’m lazy and pass the basic code of mainactivity class. Don’t pay too much attention to business logic here, as long as we can test that the basefragment in MVP can work. Look at the code:

View layer: create a new secondfragment implementation class:


package com.test.mvp.mvpdemo.mvp.v6.view;
 
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.widget.TextView;
import android.widget.Toast;
import com.test.mvp.mvpdemo.R;
import com.test.mvp.mvpdemo.mvp.v6.SecondContract;
import com.test.mvp.mvpdemo.mvp.v6.basemvp.BaseFragment;
import com.test.mvp.mvpdemo.mvp.v6.inject.InjectPresenter;
import com.test.mvp.mvpdemo.mvp.v6.presenter.SecondPresenter;
 
public class SecondFragment extends BaseFragment implements SecondContract.ISecondView {
 
 private TextView tvFragment;
 
 @InjectPresenter
 private SecondPresenter mPresenter;
 
 @Override
 protected int setLayout() {
  return R.layout.fragment_second;
 }
 
 @Override
 protected void initViews(@Nullable Bundle savedInstanceState) {
  tvFragment = $(R.id.tv_fragment);
 }
 
 @Override
 protected void initData() {
  mPresenter.handlerData();
 }
 
 @Override
 public void showDialog() {
//  Toast.makeText(getContext(), "this is Fragment", Toast.LENGTH_SHORT).show();
 }
 
 @SuppressWarnings("ConstantConditions")
 @Override
 public void succes(String content) {
  getActivity().runOnUiThread(new Runnable() {
   @Override
   public void run() {
    Toast.makeText(getContext(), "" + content, Toast.LENGTH_SHORT).show();
    tvFragment.setText(content);
   }
  });
 }
 
}

The corresponding is secondpresenter. The code of our presenter layer is as follows. The code is the same as the previous articles, which is not described here. The code is as follows:

###Presenter layer: create a new secondpresenter implementation class:

package com.test.mvp.mvpdemo.mvp.v6.presenter;
 
import com.test.mvp.mvpdemo.mvp.v6.SecondContract;
import com.test.mvp.mvpdemo.mvp.v6.basemvp.BasePresenter;
import com.test.mvp.mvpdemo.mvp.v6.model.SecondModel;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;
 
public class SecondPresenter extends BasePresenter<SecondContract.ISecondView, SecondModel> implements SecondContract.ISecondPresenter {
 
 @Override
 public void handlerData() {
  getView().showDialog();
 
  getModel().requestBaidu(new Callback() {
   @Override
   public void onFailure(Call call, IOException e) {
   }
 
   @Override
   public void onResponse(Call call, Response response) throws IOException {
    String content = response.body().string();
    getView().succes(content);
   }
  });
 }
}

Next, the rest is our model layer. We correspond to the secondmodel class, or request network data, because we previously requested the page text of Baidu homepage. In order to form a difference, I changed the URL to my blog address, ha ha. The code is as follows:

Model layer: create a new secondmodel implementation class:


package com.test.mvp.mvpdemo.mvp.v6.model;
 
import com.test.mvp.mvpdemo.mvp.v6.SecondContract;
import com.test.mvp.mvpdemo.mvp.v6.basemvp.BaseModel;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
 
public class SecondModel extends BaseModel implements SecondContract.ISecondModel {
 @Override
 public void requestBaidu(Callback callback) {
  OkHttpClient client = new OkHttpClient();
  Request request = new Request.Builder()
    .url("https://blog.csdn.net/smile_running")
    .build();
  client.newCall(request).enqueue(callback);
 }
}

Finally, there is a contract class for them, all of which are interface types. The code is as follows:

Create a new secondcontract interface class:


package com.test.mvp.mvpdemo.mvp.v6;
 
import com.test.mvp.mvpdemo.mvp.v6.basemvp.IBasePresenter;
import com.test.mvp.mvpdemo.mvp.v6.basemvp.IBaseView;
 
import okhttp3.Callback;
 
public interface SecondContract {
 interface ISecondModel {
  void requestBaidu(Callback callback);
 }
 
 interface ISecondView extends IBaseView {
  void showDialog();
 
  void succes(String content);
 }
 
 interface ISecondPresenter extends IBasePresenter {
  void handlerData();
 }
}

The subcontracting situation is the package diagram at the beginning of the article. OK, after writing the code, let’s try it.

The running situation here is: Click textview from mainactivity to jump to secondactivity. Since our secondfragment is displayed in secondactivity, the address text of my blog will be obtained from the network, and the data will be set to textview of secondfragment. The running effect is as follows:

Well, although the effect is a little simple, our basefragment has been encapsulated. After testing, it can be used. After our unremitting efforts, we have pushed forward the construction of the basic MVP framework a little bit. In the process of encapsulating basefragment, the code I wrote did make some small mistakes. This is us. The reason is that I didn’t copy the code! Ha ha ha ha, I’m so angry. It took me a lot of time to correct this mistake.

Log error cause: update UI operation in child thread.

The error code is as follows: update UI in secondfragment


 @Override
 public void succes(String content) {
  Toast.makeText(getContext(), "" + content, Toast.LENGTH_SHORT).show();
  tvFragment.setText(content);
 }

This is not very simple, it will not change!

This is not the same. The error message reported by it is not that the child thread modifies the main thread exception, but such a heap of error logs:


07-09 23:51:21.887 9769-9788/com.test.mvp.mvpdemo E/EGL_adreno: tid 9788: eglSurfaceAttrib(1319): error 0x3009 (EGL_BAD_MATCH)
07-09 23:51:21.915 9769-9788/com.test.mvp.mvpdemo E/EGL_adreno: tid 9788: eglSurfaceAttrib(1319): error 0x3009 (EGL_BAD_MATCH)
07-09 23:51:23.362 9769-9788/com.test.mvp.mvpdemo E/EGL_adreno: tid 9788: eglSurfaceAttrib(1319): error 0x3009 (EGL_BAD_MATCH)
07-09 23:51:27.742 9769-9788/com.test.mvp.mvpdemo E/EGL_adreno: tid 9788: eglSurfaceAttrib(1319): error 0x3009 (EGL_BAD_MATCH)
07-09 23:51:28.069 9769-9798/com.test.mvp.mvpdemo E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
 Process: com.test.mvp.mvpdemo, PID: 9769
 java.lang.reflect.UndeclaredThrowableException
  at $Proxy2.succes(Unknown Source)
  at com.test.mvp.mvpdemo.mvp.v6.presenter.SecondPresenter$1.onResponse(SecondPresenter.java:25)
  at okhttp3.RealCall$AsyncCall.run(RealCall.kt:138)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
  at java.lang.Thread.run(Thread.java:818)
  Caused by: java.lang.reflect.InvocationTargetException
  at java.lang.reflect.Method.invoke(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:372)
  at com.test.mvp.mvpdemo.mvp.v6.basemvp.BasePresenter$1.invoke(BasePresenter.java:31)
  at java.lang.reflect.Proxy.invoke(Proxy.java:397)
  at $Proxy2.succes(Unknown Source) 
  at com.test.mvp.mvpdemo.mvp.v6.presenter.SecondPresenter$1.onResponse(SecondPresenter.java:25) 
  at okhttp3.RealCall$AsyncCall.run(RealCall.kt:138) 
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) 
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) 
  at java.lang.Thread.run(Thread.java:818) 
  Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
  at android.os.Handler.<init>(Handler.java:200)
  at android.os.Handler.<init>(Handler.java:114)
  at android.widget.Toast$TN.<init>(Toast.java:359)
  at android.widget.Toast.<init>(Toast.java:100)
  at android.widget.Toast.makeText(Toast.java:273)
  at com.test.mvp.mvpdemo.mvp.v6.view.SecondFragment.succes(SecondFragment.java:44)
  at java.lang.reflect.Method.invoke(Native Method) 
  at java.lang.reflect.Method.invoke(Method.java:372) 
  at com.test.mvp.mvpdemo.mvp.v6.basemvp.BasePresenter$1.invoke(BasePresenter.java:31) 
  at java.lang.reflect.Proxy.invoke(Proxy.java:397) 
  at $Proxy2.succes(Unknown Source) 
  at com.test.mvp.mvpdemo.mvp.v6.presenter.SecondPresenter$1.onResponse(SecondPresenter.java:25) 
  at okhttp3.RealCall$AsyncCall.run(RealCall.kt:138) 
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) 
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) 
  at java.lang.Thread.run(Thread.java:818) 
07-09 23:51:28.126 9769-9788/com.test.mvp.mvpdemo E/EGL_adreno: tid 9788: eglSurfaceAttrib(1319): error 0x3009 (EGL_BAD_MATCH) 

First of all, I looked at the first and second error reasons in the tag. It turns out that there is a problem with the reflection block. According to the prompt position in its code, I say that the getview() method in my presenter is wrong, for example:

Click to see, it is the code of dynamic proxy. What the hell is going on here? I haven’t modified the code here. How could I be wrong?

I look back and have a look. I tried to debug the breakpoint here, but there was no result. Later, I found out that if I annotated getview(). Successes (content) in the above figure, I would not report an error. The reason is that the data here is transmitted through the network request. Our okhttp needs to be updated in the UI thread. I know this.

So remember to switch to the main thread to update UI operations. Although I made a little mistake, I thought it was the problem of dynamic agent at first, so I checked a lot of knowledge about dynamic agent. I can learn a little extra knowledge by this way. Ha ha.

The basefragment here still has the problem of code duplication, such as our dependency injection code. Let’s put it in the next article to solve this problem. This article has completed the task we should complete, and tomorrow’s work will be done the day after tomorrow, ha ha ha.

The above is the whole content of this article, I hope to help you in your study, and I hope you can support developeppaer more.

Recommended Today

MySQL indexing principle

Before we start, let’s introduce oneData structure visualization website, which can visually operate various data structures. Easy to understand the underlying principles. Meaning of index When we were in primary school, we all had the experience of using a dictionary. To find a word, we usually look it up on the index page in front […]