Teach you how to encapsulate a robust MVP framework for interface development.

Posted by phreek on Wed, 09 Feb 2022 11:19:57 +0100

In our daily development, we all know that the development frameworks of Android end include MVC, MVP and MVVM. Speaking of these frameworks, everyone must have their own views, and even many students have encapsulated them.

Here's the question: aren't they all mvvms now? Why do you still write MVP? Is it useful? There are so many wheels on the Internet. Just find one with a high star.

Use and do it yourself packaging are completely two processes, which need to consider many aspects, including many pits. At present, this framework has been applied to the company project I wrote. Taking advantage of the recent time, the problems and defects existing in the framework have been repaired after this actual battle, and some JetPack components have been added at the same time. Hope to help you

First, let's briefly introduce what MVP mode is.

The simple understanding is:

Layer P is equivalent to a middleman, shouting xxx every day without making price difference... (in daily development, P inevitably involves some logical operations, but it does not affect anything. It cannot be done for the sake of design patterns. What must be done) Floor M is an honest worker who handles all kinds of hard work Layer V is equivalent to a little sister, responsible for beauty, so it is only responsible for displaying UI To sum up the process: the little sister (layer V) is going out and lacks a lipstick. Find layer p to buy it. The middleman (layer P) receives the order and finds the worker Pony (layer M) to run errands to buy it. After layer M buys it, tell the middleman that it is ready, and the middleman will inform the little sister. Then the little sister gets her lipstick and goes out to see her little brother happily.

Let's start our code process:

Many comments have been added to the code. If you don't understand anything, please leave a message at any time. Finally, remember our principles for interface oriented development.

View interface:

The first is the V-layer interface. Note that these can be changed. The framework encapsulation does not necessarily need to follow the dead rules

public interface IView {

    /**
     * When refreshing UI
     */
    default void updateView() {

    }

    /**
     * Get Context
     *
     * @return
     */
    Context context();

	//Start Loader
    void showLoader();
	
	//Stop loader
    void stopLoader();

    /**
     * Destroy
     */
    default void onDetachView() {

    }

    /**
     * turn off keyboard
     */
    void hidekey();

}

View superclass

Knife joined me here.

/**
 * Fragment Base class
 * @author by Petterp
 * @date 2019-08-03
 */
public abstract class BaseFragment<P extends IPresenter> extends Fragment implements IView{
    private P presenter = null;
    private Unbinder unbinder = null;
    private View rootView = null;
    /**
     * Set view
     *
     * @return view
     */
    public abstract Object setLayout();


    /**
     * Create view
     *
     * @param savedInstanceState
     * @param rootView
     */
    public abstract void onBindView(@Nullable Bundle savedInstanceState, @NonNull View rootView);

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        if (setLayout() instanceof Integer) {
            rootView = inflater.inflate((Integer) setLayout(), container, false);
        } else if (setLayout() instanceof View) {
            rootView = (View) setLayout();
        } else {
            throw new ClassCastException("setLayout() must be int or View Error!");
        }

        if (presenter == null) {
        	//Get P object through annotation factory
            presenter = (P) PresenterFactoryImpl.createFactory(getClass());
        }

        if (presenter != null) {
        	//Set View
            presenter.setView(this);
            //Give life cycle
            getLifecycle().addObserver(presenter);
        }

        //Bind ButterKnife
        unbinder = ButterKnife.bind(this, rootView);
        //Fragment reclaims retained data
        setRetainInstance(true);
        //Add lifecycle
        onBindView(savedInstanceState, rootView);
        return rootView;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        setTitleToolbar();
    }

    /**
     * Immersive status bar
     */
    private void setTitleToolbar() {
        ImmersionBar.with(this)
                .titleBar(setToolbar())
                .autoDarkModeEnable(true)
                .init();
    }

    @Override
    public Context context() {
        return getContext();
    }

    /**
     * Set Toolbar
     *
     * @return
     */
    public View setToolbar() {
        return null;
    }

    /**
     * Return to P
     *
     * @return Presenter
     */
    public P getPresenter() {
        return presenter;
    }

    /**
     * Return subclass View
     *
     * @return view
     */
    protected View getRootView() {
        return rootView;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        if (unbinder != null) {
            unbinder.unbind();
            unbinder = null;
        }
        presenter = null;
        rootView = null;
    }


    @Override
    public void hidekey() {

    }


    @Override
    public void showLoader() {

    }

    @Override
    public void stopLoader() {

    }
}

Model interface

Note: RxJava+RxAndroid are integrated into the framework. RxModelinit here is used to handle some data loading tasks during initialization.

public interface IModel<P extends IPresenter> {

    /**
     * Some initialization behavior
     */
    default void initData() {

    }
    /**
     * Set P
     * @param p
     */
    void setPresenter(P p);

    /**
     * Initialization time consuming behavior
     */
    default void RxModelinit() {

    }

    /**
     * Get P-layer object
     *
     * @return P
     */
    P getPresenter();
}

Model superclass

There is nothing to note here, routine operation.

public abstract class BaseModel<P extends IPresenter> implements IModel {
    /**
     * P Layer interface
     */
    private P p;

    /**
     * Set P
     * @param iPresenter
     */
    @Override
    public void setPresenter(IPresenter iPresenter) {
        this.p= (P) iPresenter;
    }

    /**
     * Get P-layer interface
     * @return
     */
    @Override
    public P getPresenter() {
        return p;
    }
}

Presenter interface

Note: the superclass inherits DefaultLifecycleObserver because it is given a life cycle **Note: * * rx time-consuming task processing scheme has been integrated in the framework, which can be adjusted dynamically according to usage habits

public interface IPresenter<V extends IView, M extends IModel> extends DefaultLifecycleObserver {

    /**
     * Some initialization operations
     */
    default void initPresenter() {

    }

    /**
     * Do you want livebus - > to be EventBus temporarily
     *
     * @return
     */
    default boolean isLiveBus() {
        return false;
    }

    /**
     * Set View
     *
     * @param v
     */
    void setView(V v);

    /**
     * Get V
     *
     * @return V
     */
    V getView();

    /**
     * Get M
     *
     * @return M
     */
    M getModel();

    /**
     * Start Rx initialization time-consuming task
     */
    void rxStartInitData();

    /**
     * Rx At the end of the task
     */
    default void rxEndInitData() {

    }

    /**
     * rx During initialization
     */
    default void rxSpecificData() {

    }

    /**
     * Is Rx required
     *
     * @return mode
     */
    default boolean rxMode() {
        return false;
    }
}

Presenter superclass

Note: dynamic proxy is used here. The purpose of dynamic proxy is to avoid null pointer of View, so as to reduce multiple null judgment of View. Note: the framework inherits the status bar processing tool immersionbar. When using, you only need to implement setToolbar() and then pass in the corresponding view. Note: the JetPack life cycle component is used here to give the P-layer life cycle. Because it is oriented to interface development, our P-layer interface needs to inherit from DefaultLifecycleObserver.

public abstract class BasePresenter<V extends IView, M extends IModel> implements IPresenter<V, M>, InvocationHandler {

    private SoftReference mView;
    private Disposable subscribe = null;
    private M model;
    private V proxyView;


    @SuppressWarnings("unchecked")
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    @Override
    public void setView(V v) {
    	//Soft reference
        this.mView = new SoftReference(v);
        //Dynamic agent
        proxyView = (V) Proxy.newProxyInstance(v.getClass().getClassLoader(), v.getClass().getInterfaces(), this);
        //M object obtained by annotation factory
        model = (M) ModelFactoryImpl.createFactory(getClass());
        if (model != null) {
            model.setPresenter(this);
        }
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return isAttached() ? method.invoke(mView.get(), args) : null;
    }

    private boolean isAttached() {
        return mView.get() != null && proxyView != null;
    }


    @Override
    public V getView() {
        return proxyView;
    }

    @Override
    public M getModel() {
        return model;
    }


    @Override
    public void rxStartInitData() {
        subscribe = Observable
                .create(emitter -> {
                    rxSpecificData();
                    emitter.onComplete();
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnComplete(this::rxEndInitData)
                .subscribe();
    }


    @Override
    public void onStart(@NonNull LifecycleOwner owner) {

    }

    @Override
    public void onResume(@NonNull LifecycleOwner owner) {
        //Initialize some operations, basic operations
        initPresenter();
        if (model != null) {
            getModel().initData();
        }
    }

    @Override
    public void onPause(@NonNull LifecycleOwner owner) {

    }

    @Override
    public void onStop(@NonNull LifecycleOwner owner) {
        //Close the keyboard. It is recommended to add a global Activity. Here, call the hidekey method of the public view layer
        proxyView.hidekey();
    }

    @Override
    public void onDestroy(@NonNull LifecycleOwner owner) {
    	//Note that do not set the proxy View to null here
        proxyView.onDetachView();
        if (mView != null) {
            mView.clear();
            mView = null;
        }

        //If the EventBus has been turned on, turn it off
        if (isLiveBus()) {
            EventBus.clearCaches();
            EventBus.getDefault().unregister(this);
        }

        //Cancel Rx subscription
        if (subscribe != null && !subscribe.isDisposed()) {
            subscribe.dispose();
        }
        //Cancel lifecycle
        owner.getLifecycle().removeObserver(this);
    }
}

Annotation related

@CreateModel - generate layer M

@Inherited //Repeatable
@Retention(RetentionPolicy.RUNTIME) //Runtime
public @interface CreateModel {
    Class<? extends BaseModel> value();
}
public class ModelFactoryImpl {
    /**
     * Create the factory implementation class of Presenter according to the annotation
     *
     * @param viewClazz You need to create the V-layer implementation class of Presenter
     * @param <M>       Type of Model currently to be created
     * @return Factory class
     */
    @SuppressWarnings("unchecked")
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public static <M extends BaseModel> M createFactory(Class<?> viewClazz) {

        CreateModel annotation = viewClazz.getAnnotation(CreateModel.class);
        Class<M> aClass = null;
        if (annotation != null) {
            aClass = (Class<M>) annotation.value();
        }
        try {
            return aClass != null ? aClass.newInstance() : null;
        } catch (IllegalAccessException | InstantiationException e) {
            throw new RuntimeException("Presenter Creation failed!,Check whether it is declared@CreatePresenter(xx.class)annotation");
        }
    }
}

@CreatePresenter - generate P layer

@Inherited //Repeatable
@Retention(RetentionPolicy.RUNTIME) //Runtime
public @interface CreatePresenter {
    Class<? extends BasePresenter> value();
}
public class PresenterFactoryImpl {

    /**
     * Create the factory implementation class of Presenter according to the annotation
     *
     * @param viewClazz You need to create the V-layer implementation class of Presenter
     * @param <P>       The type of Presenter you are currently creating
     * @return Factory class
     */
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public static <P extends BasePresenter> P createFactory(Class<?> viewClazz) {
        CreatePresenter annotation = viewClazz.getAnnotation(CreatePresenter.class);
        Class<P> aClass = null;
        if (annotation != null) {
            aClass = (Class<P>) annotation.value();
        }
        try {
            return aClass != null ? aClass.newInstance() : null;
        } catch (IllegalAccessException | InstantiationException e) {
            throw new RuntimeException("Presenter Creation failed!,Check whether it is declared@CreatePresenter(xx.class)annotation");
        }
    }
}

The specific use process is as follows:

//Presenter class
@CreateModel(TestModels.class)
public class TestPresenter extends BasePresenter<TestControl.testView, TestControl.testModel> implements TestControl.testPresenter {

}
//Model
public class TestModels extends BaseModel<TestControl.testPresenter> implements TestControl.testModel{

}
//View
@CreatePresenter(TestPresenter.class)
public class TestFragment extends BaseFragment<TestControl.testPresenter> implements TestControl.testView {
    @Override
    public Object setLayout() {
        return R.layout.test_fragment;
    }

    @Override
    public void onBindView(@Nullable Bundle savedInstanceState, @NonNull View rootView) {

    }
    }
//Control contract interface
public interface TestControl {
    interface TestView extends IView {

    }

    interface TestPresenter extends IPresenter<testView, testModel> {
    }

    interface TestModel extends IModel {
    }
}

It's a great honor to see here to prove that this article is helpful to you.

Let me briefly talk about my idea of mobile terminal framework, some understanding in the packaging process and the pit in the actual project.

In MVP architecture, M and P are not related to each other in some pictures on the Internet. Why do you choose to be related to each other?

In the previous encapsulation, I didn't associate M with P, but in the actual development, I gradually found that P is needed in many places. If it is not associated, many logical operations must be placed in the p layer, which weakens the real role of the M layer. In fact, in MVP, many people think that p layer is the most important. In fact, it is not. P is just an intermediary. Its role is to coordinate M and V, so as to decouple M and V. Of course, you can handle some logic. Even you can put the logic on the p layer. You can't say it wrong. You can only say personal understanding.

Many people are using weak references to P objects or View objects. Is this really useful and practical?

First of all, it is useful because P holds View. Secondly, there are certain risks. There are four kinds of Java references. You can Baidu for details. If you directly use weak references, there is a risk of being recycled, so a better way is to use the reference queue (when the object is recycled, it will be put into the queue), but at the same time, you should also note that after you get(), your reference has become a strong reference.

What if I have some modules that need to be reused?

I personally recommend using the strategy mode for transformation. It is to separate the same method into an interface. Your main Fragment or Activity holds the interface object and provides the set method. Different subclasses implement the interface. Then, when using, set into the corresponding subclass instance, and then use the interface object to call the common method. Simple understanding is actually polymorphism. I originally added a public policy superclass to the framework, but the intrusion was serious, so I gave up. Of course, there are many design patterns. Sometimes no pattern is the best pattern.

Should I choose MVP or MVVM?

There is no best, only the most suitable. I think everyone wants to curse when they hear this sentence. What is it? As a passer-by, I need to tell you not to contact mature MVVM or MVP framework at the beginning, because the author considers a lot and may not be very friendly to beginners, so start from the basic tutorial. Finally, in fact, MVP and MVVM are not very different. How to use them depends on your project. If it's just learning, it's recommended to use them for actual development. If you have the same learning time, I prefer MVVM. After all, it is actually easier than MVP. This sentence may be ambiguous. Some students think that MVVM is very difficult. It sounds like the cost of learning ViewModel, DataBing and livedata is very high. But this is not the case. The more complex things look, they are often very simple after learning. Apart from DataBing, it's very fast to learn ViewModel+LiveData. Especially when you encapsulate MVP yourself, you will have the same feeling as before.

If you have any questions in use, please leave a message and hope to make progress and grow with you.

Finally, attach a link. CloudMVP