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.