12

I need to use the Context of activity in the model while using MVP in android to get the list of all the installed application.what is the correct way to access the context or any alternative to achieve the same while following the MVP pattern.

Here are the classes:

Main Activity.java

public class MainActivity extends BaseActivity
    implements MainView,View.OnClickListener {

    private MainPresenter mPresenter;
    private Button sendButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       init();
       createPresenter();
    }

    private void init(){
       sendButton= (Button) findViewById(R.id.button_send);
       sendButton.setOnClickListener(this);
    }

    private void createPresenter() {
       mPresenter=new MainPresenter();
       mPresenter.addView(this);
    }

    @Override
    public void onClick(View view) {
       switch (view.getId()){
          case R.id.button_send:
             mPresenter.onSendButtonClick();
             break;
        }
    }

   @Override
   public void openOptionsActivity() {
        Intent intent=new Intent(this,OptionsActivity.class);
        startActivity(intent);
   }
}

Main Presenter.java

public class MainPresenter extends BasePresenter {
    MainModel model;

    public void onSendButtonClick() {
       model.getListOfAllApps();
    }

    @Override
    public void addView(MainView view) {
        super.addView(view);
        model = new MainModel();
    }

}

Main Model.java

public class MainModel {

    public void getListOfAllApps(){
        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        final List pkgAppsList = getPackageManager().queryIntentActivities(mainIntent, 0);
  
    }

}

Having issue in getPackageManager().queryIntentActivities(mainIntent, 0) .how to do it as not having any context here.

Rohit Singh
  • 16,950
  • 7
  • 90
  • 88
Harish Sharma
  • 360
  • 1
  • 2
  • 7
  • 1
    Don't you have `Application class` ? – M D Aug 23 '16 at 11:37
  • I have the application class.but how to use it.Are you saying to use some constant field for context to use in application class?The problem is as per the pattern we should not use any android specific class/object in model. – Harish Sharma Aug 23 '16 at 11:40
  • Not at all. Just create applicationContext in onCreate(...) and use it – M D Aug 23 '16 at 11:41
  • I have a MainActivity, a MainPresenter and a MainModel class.Now I need to access context in Model.what is the use of applicationcontext in oncreate of activity? – Harish Sharma Aug 23 '16 at 11:46
  • post some code what you have done – Jay Shah Aug 23 '16 at 11:55
  • 4
    @MD You're totally missing the point of using MVP to separate concerns here. Since Model contains business logic it should be framework agnostic meaning it should not have direct dependency on Android specific objects, like Context – Nicolás Carrasco-Stevenson Oct 19 '16 at 23:34

2 Answers2

25

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.

Community
  • 1
  • 1
Jahnold
  • 7,623
  • 2
  • 37
  • 31
  • 3
    As [this comment](http://stackoverflow.com/questions/37137624/how-to-use-shared-preferences-in-mvp-without-dagger-and-not-causing-presenter-to#comment61849985_37138822) said, it is only hiding the dependency, which is worse. [The answer](http://stackoverflow.com/a/37216407/1276636) on the same thread is very good as it makes dependencies very clear. I found [this video](https://www.youtube.com/watch?v=-FRm3VPhseI&index=2&list=PL693EFD059797C21E) great on singletons and hidden dependencies. – Sufian Oct 11 '16 at 07:18
  • I would just make a small adjustment to point 3 and make PackageModel an interface and make getListOfAllApps() its single method. – Nicolás Carrasco-Stevenson Oct 19 '16 at 23:43
  • your first way is awful. just delete the fist way. its absolutely wrong. the presenter and model should be in pure java. the interface is the true way. – Amir Ziarati May 10 '17 at 07:37
  • @AmirZiarati Indeed it is not the best way to do it, but I mention it to show what people might consider before moving onto better options. – Jahnold May 10 '17 at 08:26
  • @Jahnold In your 3rd solution, do you consider PackageModel as MVP model? If so, then creating a PackageModel instance in MainActivity creates link between VIEW & MODEL, am I right? But as far as I know, there shouldn't be any link between VIEW & MODEL in MVP pattern. I am new in MVP topic, I haven't got this point. – Newaj Mar 13 '19 at 15:41
  • hi @Jahnold , I haven't got a point in the 3rd solution. You have created a PackageModel instance in the View & called packageModel.getListOfAllApps() in presenter. So, is PackageModel working as MVP model here? If not, then where is the MODEL part for this task here? If it is considered as Model, then creating an instance of PackageModel creates link between MVP View & Model, isn't it? Also PackageModel has android term(Context), but Model should not have android term. I am new in MVP and not getting the point here. I have a separate question with bounty, you may consider it as well. – Newaj Mar 16 '19 at 13:31
-1

Basically, you have the following options:

1) Always pass a Context to the Model. Whatever event happens in Android, you always have some kind of Context available. (And your code is invoked only in response to events.)

2) getApplicationContext() and store it for future use in a static variable.

There are the following gotchas:

An Activity is a Context, but if you store a link to an Activity, you get a memory leak. Activities are re-created when for example the screen turns. The same about contexts passed to BroadcastReceivers and other kinds of Context. All of them have a lifetime, and that lifetime is not what you need for Model.

It is possible that your application is killed and restarted by Android. In this case, some global (static) variables may be set to null. That is, they will be null unless your application happens to write something to them. In particular, a static variable pointing to the application context may become null in one of restart scenarios. Such problems are difficult to test against.

18446744073709551615
  • 16,368
  • 4
  • 94
  • 127
  • 2
    you should not use context in presenter and model. this code wont be testable. – Amir Ziarati May 10 '17 at 07:38
  • @AmirZiarati, i need to get local strings context.getString(R.string...) in model. How can i achieve this without using context as a parameter of model's constructor or using context at all? – Thracian May 18 '18 at 07:21
  • @Thracian just fetch it from the resource using the context in view layer and send it to model layer as a string dependency. – Amir Ziarati May 18 '18 at 07:31