Reputation: 11529
I've recently started developing an Android app with a model–view–presenter architecture. One issue that keeps coming up that I haven't been able to find a good solution for is passing strings from the presenter to be displayed in the view.
I am trying to keep Android system dependencies out of the presenter layer to make unit testing easier. This is simple when providing a string that comes from a server or some other external source. When I need to display a simple error message that is always the same, I can just have a method like showWhateverError()
, where the view already knows which string resource to use and can handle loading the resource itself. And when I have business logic that determines which string resource to provide the view, I can just reference the string resource ID in the presenter (although that feels wrong too).
The case that I haven't come up with a good solution for is when I need to display a string that sometimes comes from the server and is sometimes a string resource, based on some business logic. Any ideas would be helpful!
Upvotes: 2
Views: 2672
Reputation: 7683
For these situations I have additional helper class which I call a Formatter
. I pass the current state from my Presenter
to the View
which then asks the Formatter
for the appropriate strings based on that state. I think a small example will help:
You have an object which represents the data you wish to set on the View. Let's call it State
for now:
public class State {
private final boolean isServerString;
private final String serverString;
public State(boolean isServerString, String serverString) {
this.isServerString = isServerString;
this.serverString = serverString;
}
public boolean isServerString() {
return isServerString;
}
public String getServerString() {
return serverString;
}
}
In you Presenter
you would create this based on whatever logic you need and pass it to the View
:
public class MessagePresenter {
private void setMessage() {
// logic here
State state = new State(true, "Hello from the server");
view().setMessage(state);
}
}
Then in your Activity/Fragment/ViewGroup you'd have something along the lines of:
public class MyActivity extends Activity implements MessageView {
private MessageFormatter formatter;
private TextView messageTextView;
@Override
public setMessage(State state) {
String message = formatter.getMessage(state);
messageTextView.setText(message);
}
}
As you can see the view asks the Formatter
for the String to display in the TextView. The Formatter would look something like this:
public class MessageFormatter {
private Context context;
public MessageFormatter(Context context) {
this.context = context;
}
public String getMessage(State state) {
return state.isServerString()
? state.getServerString()
: context.getString(R.string.default_message);
}
}
It takes a Context
as a constructor param and, yes it does have a little bit of logic in it. However the heavy lifting logic remains in the Presenter
. Most of the time it should just be a simple boolean check.
The real power of using this method comes into play when you make a Formatter
interface and then use your Presenter
to decide which Formatter
to instantiate. As an example you can then create a HoliidayMessageFormatter
and a DefaultMessageFormatter
which would allow you to give you app a different theme based on a small logic check in the Presenter
.
Upvotes: 3
Reputation: 8882
The error message visualisation must be handled using View!
Your presenter have to call the related error method, for instance invalidEmail(), and the view have to decide how to interact with user and use UI elements to show the error.
In some implementations you may want to show an animation instead of a message! therefore sending the message from down layers to view is not right.
Here is a sample repo which can help you with learning more about MVP, Dagger, Testing and other concepts:
I hope it help :)
Upvotes: 1