In my Android app I have a multi step login procedure:
firstCall(id)
secondCall(password)
There are wrongId()
and correctId()
callbacks for 1.
There are wrongPassword()
and correctPassword()
callbacks for 2.
I have implemented this already successfully in my Android app by means of using MVP pattern.
Since I use this login procedure on different Activities (all in MVP), I have a lot of duplicate code. I extracted this login to a separat class, what would be used still in the presenter. But then I have the problem of requesting wrong passwords?
Do I need the callbacks implemented in all my presenters? How would I notify the currently used presenter of wrong passwords etc?
There are several ways you can do that, but the implementation will be affected by several factors. For more concrete example for your case you need to provide more information on the system and the requirements for it. Also some sample code will help.
One way you can do this is by using a base class for all presenters that support login. Here's an simplified example (I'll skip checks and retries for invalid input for simplicity, also you must give more details and example code so that a more detailed example that fit's your case can be given):
public interface Api {
bool isIdValid(UUID id);
Token getToken(UUID id, string password);
}
public interface LoginView {
UUID askUserForId();
string askUserForPassword();
void notifyForInvalidId();
void notifyForInvalidPassword();
void showNoMorePasswordAttemptsAllowed();
}
public class LoginPresenterBase {
private Api mApi;
private LoginView mLoginView;
public LoginPresenterBase(LoginView loginView, Api api, other stuff){
mApi = api;
mLoginView = loginView;
}
public Token doLogin() {
UUID id = null;
while(true) {
id = mLoginView.askUserForId();
// add end condition here so you don't cycle forever when the user clicks
a cancel button or whatever
if(!mApi.isIdValid(id)) {
mLoginView.notifyForInvalidId();
}
else {
break;
}
}
for(int i = 0; i < retriesCount; ++i) {
stirng password = mLoginView.askUserForPassword();
// exit if the user clicks a cancel button or closes the dialog.
Token token = null;
try {
token = mApi.getToken(id, password);
}
catch(InvalidPasswordException) {
if(i == retriesCount - 1){
// if last retry
mLoginView.showNoMorePasswordAttemptsAllowed();
}
else {
mLoginView.notifyForInvalidPassword();
}
}
return token;
}
}
}
Another way is to create a Login Process to represent the sequence of steps. Since this process will require some data to be provided (password for example) from the outside (the user entering it in UI window for example) you can use interaces or callbacks to issue requests from your Login Process for someone to provide it with the required data.
This way you can plug-in different views and/or presenters to add specific logic (password input for example) and reuse the process.
Here's an example:
public interace IdProvider {
public UUID getId();
public void invalidIdProvided(UUID);
}
public interface PasswordProvider {
public string getPassword();
public void invalidPasswordProvided(string password);
public void maxPasswordAttemptsReached();
}
public class LoginProcess {
private Api mApi;
private IdProvider mIdProvider;
private PasswordProvider mPasswordProvider;
public LoginProcess(
IdProvider idProvider, PasswordProvider passwordProvider, Api api) {
mApi = api;
mIdProvider = idProvider;
mPasswordProvider = passwordProvider;
}
public Token execute() {
UUID id = null;
while(true) {
id = mIdProvider.getId();
if(!mApi.isIdValid(id)) {
mIdProvider.invalidIdProvided(id);
// add end condition here so you don't cycle forever when the
// user clicks a cancel button or whatever
}
else {
break;
}
}
for(int i = 0; i < retriesCount; ++i) {
string password = mPasswordProvider.getPassword();
Token token = null;
try {
token = mApi.getToken(id, password);
}
catch(InvalidPasswordException) {
if(i == retriesCount - 1){
// if last retry
mPasswordProvider.maxPasswordAttemptsReached();
}
else {
mPasswordProvider.invalidPasswordProvided();
}
}
return token;
}
}
This way you can plug-in every view/presenter you wan't. Just implement the interfaces and the process will be in a separate object. You can create a base presenter that implements there interfaces if necessary.
The last solution with the LoginProcess class has the best Separation of Concerns and uses the Single Responsibility Principle.
The problem with it is that you do have to define another set of interfaces and classes.
The first one uses less code and still works. You can reuse the presenter and you can create a view that implements the LoginView interface that you can reuse or use as a base class.