Android MVP - Should avoid using R.string references in presenter?
Solution 1
I consider that there's no reason to call any android code in Presenter (But you always can do it).
So in your case:
View / activity onCreate() calls -> presenter.onCreate();
Presenter onCreate() calls -> view.setTextLabel() or whatever you want in the view.
Always decouple Android SDK from presenters.
In Github, you can find some examples about MVP:
Solution 2
it's better to not use context and all object that depends on android sdk in presenter. I send id of the String and view cast it into string. like this->
getview().setTitle(R.string.hello);
and get this on view like this
@Override
public void setTitle(int id){
String text=context.getString(id);
//do what you want to do
}
With this approach you can test your method in presenter. It depends on R object but it's okay. all MVP classes placed in presentation layer in uncle bob clean architecture so you can use android objects like R class. but in domain layer you have to use only regular java objects
Update
For those who want to reuse their code in other platforms you can use a wrapper class for mapping the id or enum types to the resources and get the string.
getView().setTitle(myStringTools.resolve(HELLO));
The string resolver method is like this and the class can provided by View and DI into presenters.
Public String resolve(int ourID){
return context.getString(resourceMap.getValue(ourID));
}
But I do not recommend this in most of cases because of over engineering! you never need exact presentation code in other platforms in most of the times so: Better solution would be something like mocking that R class in other platforms because R class is already like a wrapper. You Should write your own R in other platform.
Solution 3
Your presenter
should NOT need to know about how to show the details of showing the UI, and as such the R.string
references.
Let's suppose you encounter a network issue and you want to show the user a network error message.
The first (wrong IMO) thing would be to get the context from the view
and call some method like this in your presenter
:
public void showNetworkError(){
presenter.showMessage(view.getResources().getString(R.string.res1));
}
In which you're using the context
from your view
-- which is either an Activity
or a Fragment
.
Now what if you're told to change the copy content from R.string.res1
to R.string.res2
? which component should you change?
The view
. But is that necessary?
I believe not, because what is important for the presenter
is that the view
shows a message regarding network error, be it "Network error! please try again" or "There is a network error. Please try later."
So what is the better way?
Change your presenter
to the following:
public void showNetworkError(){
view.showNetworkErrorMessage();
}
and leave the implementation details to the view
:
public void showNetworkErrorMessage(){
textView.setText(R.string.resX)
}
I have written a complete article on MVP here, just in case.
Solution 4
This will be a long post about how to structure MVP project before getting into solving your problem at very last of my answer.
I just report MVP structure here how to structure MVP project from my own answer.
I often put business logic code in Model Layer (don't make confusion with model in database). I often rename as XManager
for avoiding confusion (such as ProductManager
, MediaManager
...) so presenter class just uses for keeping workflow.
The rule of thumb is no or at least limit import android package in presenter class. This best practice supports you easier in testing presenter class because presenter now is just a plain java class, so we don't need android framework for testing those things.
For example here is my mvp workflow.
View class: This is a place you store all your view such as button, textview ... and you set all listeners for those view components on this layer. Also on this View, you define a Listener class for presenter implements later. Your view components will call methods on this listener class.
class ViewImpl implements View {
Button playButton;
ViewListener listener;
public ViewImpl(ViewListener listener) {
// find all view
this.listener = listener;
playButton.setOnClickListener(new View.OnClickListener() {
listener.playSong();
});
}
public interface ViewListener {
playSong();
}
}
Presenter class: This is where you store view and model inside for calling later. Also presenter class will implement ViewListener interface has defined above. Main point of presenter is control logic workflow.
class PresenterImpl extends Presenter implements ViewListener {
private View view;
private MediaManager mediaManager;
public PresenterImpl(View, MediaManager manager) {
this.view = view;
this.manager = manager;
}
@Override
public void playSong() {
mediaManager.playMedia();
}
}
Manager class: Here is the core business logic code. Maybe one presenter will have many managers (depend on how complicate the view is). Often we get Context
class through some injection framework such as Dagger
.
Class MediaManagerImpl extends MediaManager {
// using Dagger for injection context if you want
@Inject
private Context context;
private MediaPlayer mediaPlayer;
// dagger solution
public MediaPlayerManagerImpl() {
this.mediaPlayer = new MediaPlayer(context);
}
// no dagger solution
public MediaPlayerManagerImpl(Context context) {
this.context = context;
this.mediaPlayer = new MediaPlayer(context);
}
public void playMedia() {
mediaPlayer.play();
}
public void stopMedia() {
mediaPlayer.stop();
}
}
Finally: Put those thing together in Activities, Fragments ... Here is the place you initialize view, manager and assign all to presenter.
public class MyActivity extends Activity {
Presenter presenter;
@Override
public void onCreate() {
super.onCreate();
IView view = new ViewImpl();
MediaManager manager = new MediaManagerImpl(this.getApplicationContext());
// or this. if you use Dagger
MediaManager manager = new MediaManagerImpl();
presenter = new PresenterImpl(view, manager);
}
@Override
public void onStop() {
super.onStop();
presenter.onStop();
}
}
You see that each presenter, model, view is wrapped by one interface. Those components will called through interface. This design will make your code more robust and easier for modifying later.
In short, in your situation, I propose this design:
class ViewImpl implements View {
Button button;
TextView textView;
ViewListener listener;
public ViewImpl(ViewListener listener) {
// find all view
this.listener = listener;
button.setOnClickListener(new View.OnClickListener() {
textView.setText(resource_id);
});
}
}
In case the logic view is complicated, for example some conditions for setting value. So I will put logic into DataManager
for getting back text. For example:
class Presenter {
public void setText() {
view.setText(dataManager.getProductName());
}
}
class DataManager {
public String getProductName() {
if (some_internal_state == 1) return getResources().getString(R.string.value1);
if (some_internal_state == 2) return getResources().getString(R.string.value2);
}
}
So you never put android related-thing into presenter class. You should move that to View
class or DataManager
class depend on context.
This is a very long post discuss in detail about MVP and how to solve your concreted problem. Hope this help :)
![Scott Merritt](https://i.stack.imgur.com/axjB3.jpg?s=256&g=1)
Scott Merritt
Most interested in iOS/Android native development. Have worked mostly in the financial industry on apps with lots of users.
Updated on June 15, 2022Comments
-
Scott Merritt about 2 years
In an attempt to entirely decouple the Android SDK from my presenter classes, I'm trying to figure out the best way to avoid accessing resource IDs which we normally use R for. I thought I could just create an interface to access things like string resources, but I still need IDs to reference the strings. If I were to do something like...
public class Presenter { private MyView view = ...; private MyResources resources = ...; public void initializeView() { view.setLabel(resources.getString(LABEL_RES_ID); } }
I still have to have
LABEL_RES_ID
and then map it toR.string.label
in my resources bridge. It's cool because I could swap it out when unit testing with something else, but I don't want to manage another mapping to the string value.If I give up and just use the R.string values, my presenter is bound to my view again. That's not ideal? Is there an easier solution that people use to get around this in order to keep them out of the presenter. I don't want to manage strings in a way outside of what Android provides, because I still want to throw them in layout files and get the benefit of internationalization, etc. I want to make a dumb unit test that can work with this presenter without having to have the Android SDK generate the R.java files. Is this too much to ask?
-
Joao Sousa about 9 yearsCould you specify please the argument that goes in
view.setTextLabel()
? Because if you do, that would answer the question. Basically imagine if you have logic in your presenter to display either messageR.string.a
orR.string.b
. How would you pass this string to be displayed by the view? -
PaNaVTEC about 9 yearsYour View needs to ve Passive, and your Presenter active, so, the presenter knows what to present and the view knows how to present it. if you want to show an error in your view from your presenter you need to do a call like: view.showContactLoadingError(); and your view can setup the message depending on the context of the view. I've updated the samples with another repo that I made.
-
Joao Sousa about 9 yearsThanks @PaNaVTEC, that's exactly the answer I was looking for and it makes sense.
-
baybora.oren about 8 yearsif you want to log for error and use resource to get error string then ?
-
PaNaVTEC about 8 yearsDepending on the view you maybe will display different error messages, is the same to show an error. So it can be view responsability. If this is not your case, you can wrap R.string and send a collaborator to your presenter.
-
WindRider about 8 yearsWhat if my Presenter is intended to dynamically generate a map marker popup with very simple HTML containing some localized strings. How can I generate it without access to resources or how to give access to the strings in clean way?
-
WindRider about 8 yearsI think to use a dedicated formatter class that is technically in the View side. The presenter will delegate most of the work to render the HTML snippet to it. It has an access to the Context. With DI it might be even cleaner.
-
Scott Merritt almost 8 yearsStill doesn't seem right. You're still dependent on code that was generated by the Android SDK. I think PaNaVTEC has an answer above, and although it seems like the "right" way to do it, it does seem like a lot of work to get these references out of the presenter. If you're referencing the R file, it seems like you're presenter is now dependent on Android functionality (at least code that gets generated from the Android SDK).
-
Siavash Abdoli almost 8 years@zoonsf you can't make decisions on logic in activity. There is no problem for using resources and you can even use resources in unit test. MVP is on outer layout of uncle Bob clean architecture and dependend on android so using resources in presenter is ordinary and correct
-
Przemo over 6 yearsApart from the fact that presenter shouldn't have android callbacks (strange to test that, and it's moving Android spaghetti to Presenters) I like your solution. Although, what to do if those values haven't got any logic and there are many of them? Use R.string.* in Manager or Presenter? I'd rather kept them in Presenter to avoid unnecessary layer of abstraction AKA code envy. But on the other hand they're generated by Android... Coding guidelines stands in opposition to each other.
-
Farid over 2 yearsHow the hack you made this assumption "There is no problem for using resources"?
-
Siavash Abdoli over 2 yearsAsk your question in a better way and I did explain that with clean architecture method and in my experience presentation layer should be different for every platform and I introduce a way to even reuse it for later solution. If you really want to know the reason, text me on LinkedIn So I can explain in Persian. also, that's heck not hack :D