Android MVP: safe use Context in Presenter

前端 未结 4 690
悲&欢浪女
悲&欢浪女 2021-01-17 10:02

In my app I work with ContentProvider and use LoaderManager.LoaderCallbacks.

Fragment (View)



        
相关标签:
4条回答
  • 2021-01-17 10:13

    Adding context to Presenter is not good since, the presenter is responsible for business logic. To deal with context, you need to have the Fragment/Activities make use of Callbacks with the help of interfaces which will state what actions need to be perform by the activity/fragment when dealing with views. Fragment / Activities are responsible to provide Context.

    Example:

    interface BaseContract {
            interface BaseView {
                //Methods for View
                void onDoSomething();
            }
    
            interface BasePresenter {
                void doSomething();
    
            }
        }
    
        class BaseMainPresenter implements BaseContract.BasePresenter {
            BaseContract.BaseView view;
    
            BaseMainPresenter(BaseContract.BaseView view) {
                this.view = view;
            }
    
            @Override
            public void doSomething() {
                if (view != null)
                    view.onDoSomething();
            }
        }
    
        class DemoClass implements BaseContract.BaseView {
    
            //Create object of Presenter 
    
            /****
             * Example :
             * BaseMainPresenter baseMainPresenter = new BaseMainPresenter(this);
             */
            @Override
            public void onDoSomething() {
                //Deal with Context here.
            }
        }
    
    0 讨论(0)
  • 2021-01-17 10:23

    Code like this

    Loader loader = new ArticleCatalogLoader(context, categoryId);
    

    leads to untestable code. You should avoid creating "business" objects in your code and let anyone else do it for you (any DI framework such as Dagger 2 would be a better option than handling it yourself)

    Having said that, your problem is something that DI has solved a long time ago. Do you need a fresh new instance of any object? Use a Provider

    A Provider is an object that "provides" instances of objects. So instead of having

    Loader loader = new ArticleCatalogLoader(context, categoryId);
    

    you will have

    Loader loader = loaderProvider.get(categoryId);
    

    So the only thing you need is something like this:

    public class ArticleCatalogPresenter ... {
        ...
        private final Provider<Loader> loaderProvider;
    
        public ArticleCatalogPresenter(Provider<Loader> loaderProvider, ...) {
            this.loaderProvider = loaderProvider;
            ...
        }
    
        private Loader onCreateArticleCatalogLoader(Bundle args) {    
            int categoryId = args.getInt(CATEGORY_ID);
            Loader loader = loaderProvider.get(categoryId); // no context needed anymore!
            return loader;
        }
    
    }
    
    0 讨论(0)
  • 2021-01-17 10:23
    public class ArticleCatalogPresenter extends BasePresenter
            implements LoaderManager.LoaderCallbacks<Cursor> {
    
        View view;             
        ...
        private Loader onCreateArticleCatalogLoader(Bundle args) {    
                int categoryId = args.getInt(CATEGORY_ID);
                Loader loader = new ArticleCatalogLoader(context, categoryId); // need Context
                return loader;
        }
    }
    

    So you want the context in your Presenter to build a new instance of ArticleCatalogLoader. Right?

    If so, pass the instance to the Presenter through constructor. So in your Activity or DI container when you want to build the Presenter object, do something like:

    ArticleCatalogPresenter articleCatalogPresenter=new ArticleCatalogPresenter(articleCatalogView,new ArticleCatalogLoader(context,categoryId));
    

    This way your Presenter will not be dependent on context and will be fully testable.

    About your concern on memory leak, you can easily avoid that by listening to onStop() in your View and then call the corresponding method in your Presenter to cancel any network request or context dependent task.

    I have written an MVP library which helps a lot with saving the amount of boilerplate needed for MVP a well as preventing memory leaks.

    0 讨论(0)
  • 2021-01-17 10:25

    Just don't register your presenter as Android specific callback target (e.g. BroadcastReceiver, LoaderManager.LoaderCallbacks etc.). Handle the callback methods in your View (Fragment or Activity) and pass all related data to the presenter.

    If you need Context for object creation, let your view create this object (as it has a reference to the Context). In your case the call

    Loader loader = new ArticleCatalogLoader(context, categoryId)
    

    should be refactored to

    view.createLoaderForCategory(categoryId)
    
    0 讨论(0)
提交回复
热议问题