16

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

Fragment (View)

public class ArticleCatalogFragment extends BaseFragment
        implements ArticleCatalogPresenter.View,
        LoaderManager.LoaderCallbacks<Cursor> {

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return onCreateArticleCatalogLoader(args);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {        
         data.registerContentObserver(new LoaderContentObserver(new Handler(), loader));
         updateUI(data);        
    }   

    private Loader onCreateArticleCatalogLoader(Bundle args) {
            int categoryId = args.getInt(CATEGORY_ID);
            Loader loader = new ArticleCatalogLoader(this.getActivity(), categoryId);            
            return loader;
    }

}

From point of view MVP I need:

Presenter

public class ArticleCatalogPresenter extends BasePresenter
        implements LoaderManager.LoaderCallbacks<Cursor> {

    View view;

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return onCreateArticleCatalogLoader(args);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {        
         data.registerContentObserver(new LoaderContentObserver(new Handler(), loader));
         view.updateUI(data);        
    }               

    private Loader onCreateArticleCatalogLoader(Bundle args) {    
            int categoryId = args.getInt(CATEGORY_ID);
            Loader loader = new ArticleCatalogLoader(context, categoryId); // need Context
            return loader;
    }


    interface View {
        updateUI(Cursor data)
    }

}

So, I need a context in Presenter.

There are some nuances:

  1. Presenter know about the Context - it is bad, Presenter should not know about the Android.

  2. Having a Context in Presenter can lead to memory leak.

I am now worried about how to avoid problems such as memory leaks, and how best pass Context in Presenter, use Application Context or Activity/Fragment?

Alexandr
  • 1,891
  • 3
  • 32
  • 48
  • 1
    App context is the way to go. If the view needs the activity context it can store it on its own (passed in the constructor), just make sure you don't hold a strong reference to the view (regardless) if your presenter survives the activity/fragment. – JohanShogun Aug 11 '15 at 20:54
  • Another thought is that you could let your activity/fragment take the role of the presenter. To me it seems that you've made your fragment take the role of the view, that's a bit odd as the basic functionality of the fragment pretty well over lapse that of the presenter. Your view is in the xml files and view subclasses. – JohanShogun Aug 11 '15 at 21:01
  • Thank you for feedback. I have situation when part business logic in Presenter (biggest part) and another part in Fragment (work with CursorLoader) and this creates problems. I want to move all business logic in Presenter. – Alexandr Aug 11 '15 at 21:09
  • As stated above, make your fragment the presenter instead of the view. The mvp pattern is only a good fit for android because of how activities/fragments are set up. Your view is the xml (etc) files. Also, consider making a business rules engine for your business logic instead of scattering it around in a class responsible presenting it to the view. For the record a cursor adaptor is not business logic, it's presenter logic. – JohanShogun Aug 11 '15 at 21:15
  • 4
    Hi @Alexandr did you find any good solution? – Sergey B. Feb 28 '16 at 20:42
  • 2
    Hi! Now I have changed the architecture, and I don't have a context in the presenter. But if such a issue was, I would like to use dependency injection and the dagger. Dagger help you to inject context to anywhere, and it is easy to ensure that this is the app context. – Alexandr Feb 28 '16 at 20:55
  • Hey, if you use a concrete View type instead of interface, you could just call mView.getContext() whenever is needed. – WindRider Apr 01 '16 at 16:48
  • I made the presenter call a method from my view interface that returns the `CursorLoader` but I don't know if this is a good solution... – Cassio Landim Sep 21 '16 at 00:58
  • This must help https://stackoverflow.com/questions/39100105/need-context-in-model-in-mvp – fvolodimir Jun 21 '17 at 13:03

4 Answers4

3

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.
        }
    }
Lucas Holt
  • 3,826
  • 1
  • 32
  • 41
0

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)
artkoenig
  • 7,117
  • 2
  • 40
  • 61
0

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;
    }

}
Alberto S.
  • 7,409
  • 6
  • 27
  • 46
0
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.

Ali Nem
  • 5,252
  • 1
  • 42
  • 41