Need Context in Model in MVP

I answered a similar question here which you may want to have a look at too. I'll give the breakdown of how I think you could solve this particular problem though.

Use a static context from the Application class

This method would work but I'm not fond of it. It makes testing harder and couples your code together.

public class App extends Application {

    private static Context context;

    public static Context getContext() {
        return context;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        context = getApplicationContext();
    }
}

Then in your MainModel:

public class MainModel {

    public List<String> getListOfAllApps(){

        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        final List<ResolveInfo> pkgAppsList = App.getContext().getPackageManager().queryIntentActivities(mainIntent, 0);

        List<String> results = new ArrayList<>();
        for (ResolveInfo app : pkgAppsList) {
            results.add(app.resolvePackageName);
        }
        return results;
    }
}

Now we've got that out the way, let's look at some better options.

Do it in the Activity

So your Activity implements your View. It's probably doing a few Anrdoidy things too such as onActivityResult. There's an argument for keeping Android code in the Activity and just accessing it through the View interface:

public interface MainView {

    List<String> getListOfAllApps();
}

The Activity:

public class MainActivity extends BaseActivity implements MainView {

    //..

    @Override
    public List<String> getListOfAllApps(){

        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        final List<ResolveInfo> pkgAppsList = getPackageManager().queryIntentActivities(mainIntent, 0);

        List<String> results = new ArrayList<>();
        for (ResolveInfo app : pkgAppsList) {
            results.add(app.resolvePackageName);
        }
        return results;
    }

    //..
}

And the Presenter:

public class MainPresenter extends BasePresenter {

    public void onSendButtonClick(){

        view.getListOfAllApps();
    }
}

Abstract the details in a separate class

Whilst the last option doesn't break the rules of MVP it doesn't feel quite right as getting a list of packages isn't really a View operation. My preferred option is to hide the use of Context behind an interface/class.

Create a class PackageModel (or whatever name takes your fancy):

public class PackageModel {

    private Context context;

    public PackageModel(Context context) {
        this.context = context;
    }

    public List<String> getListOfAllApps(){

        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        final List<ResolveInfo> pkgAppsList = context.getPackageManager().queryIntentActivities(mainIntent, 0);

        List<String> results = new ArrayList<>();
        for (ResolveInfo app : pkgAppsList) {
            results.add(app.resolvePackageName);
        }
        return results;
    }
} 

Now have your Presenter require this as a constructor parameter:

public class MainPresenter extends BasePresenter {

    private PackageModel packageModel;

    public MainPresenter(PackageModel packageModel) {
        this.packageModel = packageModel;
    }

    public void onSendButtonClick(){

        packageModel.getListOfAllApps();
    }
}

Finally in your Activity:

public class MainActivity extends BaseActivity implements MainView {

    private MainPresenter presenter;

    private void createPresenter() {

        PackageModel packageModel = new PackageModel(this);
        presenter = new MainPresenter(packageModel);
        presenter.addView(this);
    }
}

Now the use of Context is hidden from the Presenter and it can carry on without any knowledge of Android. This is known as constructor injection. If you're using a dependency injection framework it can build all the dependencies for you.

If you wanted to you could make an interface for PackageModel but I don't think it's really necessary as a mocking framework like Mockito can create a stub without using an interface.

Tags:

Mvp

Android