Skip to content

How to deal with login? #158

Open
Open
@ghost

Description

Introduction:

At the data layer I have retrofit-interface. For example:

public interface SomeApi {
    @POST(...)
    Observable login(LoginRequest body);
}

At the domain layer I have AccountManger:

public class AccountManager {
    private SomeApi api;
    @Inject
    public AccountManager(SomeApi api) { // errot: SomeApi out of classpath
        this.api = api;
    }

    public void login(String login, String password, Subscriber<Void> subscriber) {
        api.login(...) ...
    }
}

And then I would use AccountManagerin presentation layer.
But. Nope. Compile time error.

SomeApi is in data module classpath, because domain module has no gradle dependency on data module as data module has android dependency while domain module hasn't.

There is my solution:

Create LoginApi interface:

public interface LoginApi {
    Observable login(LoginRequest body);
}

Use it in AccountManager:

public class AccountManager {
    private LoginApi api;
    @Inject
    public AccountManager(LoginApi api) {
        this.api = api;
    }
    ...
}

Create implementation of LoginApi on data-layer that use SomeApi:

public class LoginApiImpl implement LoginApi {
    private SomeApi api;
    @Inject
    public LoginApiImpl(SomeApi api) {
        this.api = api;
    }
    Observable login(LoginRequest body) {
        return api.login(body);
    }
}

And inject it via Dagger 2 in presentation layer.

Any other ideas?

Activity

Weava

Weava commented on Jun 29, 2016

@Weava

It seems like your original solution had a data module dependency trying to be used in the domain module. In this architecture, that is not possible.

To keep in-line with what @android10 has created, you may have to create a LoginRepository (or just add a login function to the UserRepository in the existing implementation) like so:

public interface LoginRepository {
    Observable logUserIn(String email, String password)
}

Still in the domain layer, have a LoginUseCase that uses the LoginRepository as a component, like so:

public class LoginUseCase extends UseCase {

  private final int userId;
  private final LoginRepository loginRepository;
  private final String email;
  private final String password;

  @Inject
  public LoginUseCase(int userId, LoginRepository loginRepository,
      ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread, 
       String email, String password) {
    super(threadExecutor, postExecutionThread);
    this.userId = userId;
    this.loginRepository = loginRepository;
    this.email = email;
    this.password = password;
  }

  @Override protected Observable buildUseCaseObservable() {
    return this.loginRepository.logUserIn(email, password);
  }
}

The data layer process might be a bit more involved. This is where you would actually implement your repository pattern for the login case. If you want some sort of login persistence, you will need to implement some form of SharedPreferences or SQLite for storing the data on a successful login, as well as an API to handle the initial request. I won't get too much into that here, as it is quite involved depending on your implementation.

In the presentation layer, you would inject your LoginRepository from the domain layer to use wherever that dependency is needed. It would be advantageous to check if a user is logged in, and have signOut and other important methods in the repository to maintain persistence.

This isn't a great implementation by any means, and may even be too complex for your needs. I'm still learning how this architecture works myself, and this is just how I see that a login implementation may work.

Hope this helps.
Let me know if you have any questions.

ghost

ghost commented on Jun 29, 2016

@ghost

Yep, thanks, but I already came up with the same solution :)
(it described under There is my solution:).
But Repository mustn't do things like authentification. It must just to provide data. In my case LoginRepository is named as LoginApiMediator.

As I think, UseCases is too complex and I came up with Managers. For example, AccountManager does functions of LoginUseCase, LogoutUseCase, ChangePasswordUseCase etc

Some code as bonus:

@Module
public class AccountManagerModule {

    @PerApplication
    @Provides
    public AccountApiMediator provideAccountApiMediator(AccountApi accountApi) {
        return new AccountApiMediatorImpl(accountApi);
    }

    @PerApplication
    @Provides
    public AccountManager provideAccountManager(
            @IoScheduler Scheduler ioScheduler,
            @UiScheduler Scheduler uiScheduler,
            AccountApiMediator accountApiMediator,
            AccountManagerMetaDataStorage metaDataStorage
    ) {
        return new AccountManager(ioScheduler, uiScheduler, accountApiMediator, metaDataStorage);
    }

}
public class AccountApiMediatorImpl implements AccountApiMediator {

    private AccountApi accountApi;

    public AccountApiMediatorImpl(AccountApi accountApi) {
        this.accountApi = accountApi;
    }
    private LoginResponseMapper loginResponseMapper = new LoginResponseMapper();

    @Override
    public Observable<LoginData> login(String login, String password) {
        return accountApi.login(new LoginRequest(login, password));
    }

    @Override
    public Observable<Void> logout() {
        return accountApi.logoff();
    }

    @Override
    public Observable<Void> changePassword(String oldPassword, String newPassword) {
        // newPassword and newPassword2 are equals as it already checked at presentation level and there is no needs to check it on server
        return accountApi.changePassword(new ChangePasswordRequest(oldPassword, newPassword, newPassword));
    }
}

Dharmendra10

Dharmendra10 commented on Jun 30, 2016

@Dharmendra10

@TRY4W Hi, if you can provide some more code or demo to deal with the login API then it will be helpful to me.

android10

android10 commented on Jul 1, 2016

@android10
Owner

Retrofit or any other http library is an implementation detail. Basically from my perspective you should try to follow up what is done with other use cases, to keep consistency across the codebase.

LoginView -> LoginPresenter -> LoginUseCase -> UserRepository 

I would reuse the UserRepository since it makes sense in this case:

userRepository.loginUser(user);

At a data source level you would get the user data from the local storage based on your business rules, otherwise you might try to connect to the api. And of course afterwards go all the way up back to the presentation layer where you would reflect the result at UI level.

android10

android10 commented on Jul 1, 2016

@android10
Owner

I would also quote this from @Trikke:

Well, the first question should always be, do i really need a context here and why? Since i don't know how your SessionManager works, i can only guess. So the first thing to do is see if you can modify your Managers to work without.

You shouldn't be creating layers for any type of generalised action. Stuff like Security is actually a cross-cutting concern. This means that it is a topic that doesn't belong in a specific layer.

So your Session logic and Authorization logic belongs in many layers. For example :

  • Part of user authorisation is a login screen, and this is presentation logic
  • The bit where you check the details and pass them on to the data layer, or infrastructure layer belongs > in the business layer.
  • If you store your Session/User locally, this belongs in the data layer.

The hard part for you seems to be that Authorization seems tightly coupled with your REST service. So it would seem logical to have the Session Manager (preferably without a Context) in the same layer as your REST service. This can be data, or infrastructure.

And if you wanna follow up the discussion, here it is:
#151

tigerteamdc2

tigerteamdc2 commented on Apr 12, 2017

@tigerteamdc2

Hi @android10
Suppose that I have SessionManager on the data layer to control user state on the application.
Somewhere on presenter I would like to check whether the user logged in or not.
How can I handle that case?

android10

android10 commented on Apr 12, 2017

@android10
Owner

@tigerteamdc2 I would encapsulate everything in a UseCase class and inject it wherever you need.

tigerteamdc2

tigerteamdc2 commented on Apr 12, 2017

@tigerteamdc2

@android10
How could I do using UseCase?
Could I just do something like

if (sessionManager.isLoggedIn()) {
  // Do something
} else {
  // Go to login page
}

Please give some advices on this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @Dharmendra10@android10@Weava@tigerteamdc2

        Issue actions

          How to deal with login? · Issue #158 · android10/Android-CleanArchitecture