New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Best practice to pass dynamic parameters to an UseCase? #32

Open
rockerhieu opened this Issue Jul 5, 2015 · 23 comments

Comments

Projects
None yet
10 participants
@rockerhieu

rockerhieu commented Jul 5, 2015

As in the example, usecase's parameters are set via UserModule:

@Module
public class UserModule {
  private int userId = -1;
  public UserModule() {}

  public UserModule(int userId) {
    this.userId = userId;
  }

  @Provides @PerActivity @Named("userDetails") UseCase provideGetUserDetailsUseCase(
      UserRepository userRepository, ThreadExecutor threadExecutor,
      PostExecutionThread postExecutionThread) {
    return new GetUserDetailsUseCase(userId, userRepository, threadExecutor, postExecutionThread);
  }
}

But there are some cases when buildUseCaseObservable depends on some dynamic parameters. I tried to create an interface to provide these parameters and let the view (in MVP) implement it and pass them to UserModule. With this approach if the view is a Fragment then I have to re-create UserModule again in the Fragment.

public interface UserIdProvider {
  int getUserId();
}

Any suggestion, recommendation?

@rockerhieu rockerhieu changed the title from Best practice to pass parameter to an UseCase? to Best practice to pass dynamic parameters to an UseCase? Jul 5, 2015

@android10

This comment has been minimized.

Owner

android10 commented Aug 10, 2015

So in that case, buildUserCaseObservable is no longer useful and you will have to pass them via setter (with defensive code and failing fast in case all the dynamic parameters are not satisfied).
I used this approach due to dagger instantiating my objects thus, being able to pass the user id via module constructor parameter.

@android10 android10 closed this Aug 10, 2015

@spirosoik

This comment has been minimized.

spirosoik commented Sep 10, 2015

@android10 yes that's true, but how do you pass dynamics parameters for login process for example? I mean in this case there isn't a predefined parameter but you must setup the submitted details? Which is your approach on this? I know that this is a question but it's really interesting to give us an example.

For this case the Dagger module must be dynamic so I suppose that there is 3 solutions (I think):

  1. PerFragment modules something like that:
@PerFragment
@Component(dependencies = ApplicationComponent.class, modules = FragmentModule.class)
public interface FragmentComponent {
  //Exposed to sub-graphs.
  Fragment fragment();
}

@Module
public class FragmentModule {
  private final Fragment fragment;

  public FragmentModule(Fragment fragment) {
    this.fragment = fragment;
  }

  @Provides @PerFragment Fragment fragment() {
    return this.fragment;
  }
}

@PerFragment
@Component(dependencies = ApplicationComponent.class, modules = {FragmentModule.class, LoginFragmentModule.class})
public interface LoginFragmentComponent extends FragmentComponent {

  void inject(UserLoginFragment userLoginFragment);

}

@Module
public class LoginFragmentModule {

  private String username = "";
  private String password = "";


  public LoginFragmentModule(String username, String password) {
    this.username = username;
    this.password = password;
  }

  @Provides @PerFragment @Named("userLogin") UseCase provideUserLoginUseCase(
      UserRepository userRepository, ThreadExecutor threadExecutor,
      PostExecutionThread postExecutionThread) {
    return new UserLogin(username, password,userRepository, threadExecutor, postExecutionThread);
  }
}
  1. Observer to get the changes and re-initialises the Activity dagger module.
  2. Or to change dynamically the component into the Fragment during the action. For example I can do into the fragment this one as below! Not sure if it will work but with this one we are re-creating the component during the action. Basically this will be done in every case (for @PerFragment also)
DaggerUserComponent.builder()
        .applicationComponent(getApplicationComponent())
        .activityModule(getActivityModule())
        .userModule(new UserModule(username, pass))
        .build();

or ????

There is a problem only this because you can't use multiple scopes for example for data mappers. So we must remove scopre for datamappers for example in order to be able to seen into the both scopes! Right? Any other suggestions maybe cleaner than this one?

@spirosoik

This comment has been minimized.

spirosoik commented Sep 13, 2015

@android10 please when you have free time it will be nice to give us a better approach by your side.

@spirosoik

This comment has been minimized.

spirosoik commented Oct 27, 2015

@android10 I know that is closed this issues but I want to know if our approach is acceptable.

Currently we have multiple forms so for example I have an invite form with multiple attrs.

Let's Say I have InviteActivity InviteView InvitePresenter InviteComponent in their referral package explicitly.

In my InviteModule of dagger I have something like this:

/**
 * Dagger module that provides user related collaborators.
 */
@Module public class InviteModule {

  private String email;
  private String businessName;

  public InviteModule() {
  }

  public InviteModule(String email, String businessName) {
    this.email = email;
    this.businessName = businessName;
  }

  @Provides @PerActivity @Named("userLogin") UseCase provideInviteBusinessUseCase(InviteBusiness sendInviteBusiness) {
    return new InviteBusiness(this.email, this.businessName, UserRepository, threadExecutor,
        postExecutionThread);
  }
}

In the InviteComponent I have this

@PerActivity
@Component(dependencies = ApplicationComponent.class, modules = {
    ActivityModule.class, UserModule.class
})
public interface InviteComponent extends ActivityComponent {

  void inject(InviteBusinessFragment inviteBusinesssFragment);

  void plus(InviteModule userModule);
}

So on click of the form I am just doing this:

this.getComponent(InviteComponent.class).plus(new InviteModule(textemail, textBusinessName);

in order to use the attributes into the UseCase. Any suggestion for a different approach?

@MehdiChouag

This comment has been minimized.

MehdiChouag commented Nov 28, 2015

@spirosoik Does the solution example in your last comment works nice with your needs ?

@spirosoik

This comment has been minimized.

spirosoik commented Nov 29, 2015

@MehdiChouag It's easier to keep the abstraction as it is with buildUseCaseObservable() but you can use this. Remember that in this method you must pass a Domain model not simple parameters.

protected abstract Observable buildUseCaseObservable(Object...param);

or you can re-create the component during login

DaggerUserComponent.builder()
        .applicationComponent(getApplicationComponent())
        .activityModule(getActivityModule())
        .userModule(new UserModule(user))
        .build();

Another way is to use @PerFragment scope

I am suggesting the first solution.

@android10 @lalongooo if you have a better solution than the first one, I would love to listen.

@MehdiChouag

This comment has been minimized.

MehdiChouag commented Nov 29, 2015

@spirosoik For me the first solution seems to be more appropriate.

Rather using Object, we may using template for parameters, like :

public abstract class UseCase<T> {
...
protected abstract Observable buildUseCaseObservable(@Nullable T... params);

public void execute(Subscriber UseCaseSubscriber, @Nullable T... params) {
    mSubscription = buildUseCaseObservable(params).subscribeOn(mExecutionThread.getScheduler())
        .observeOn(mPostExecutionThread.getScheduler())
        .subscribe(UseCaseSubscriber);
  }
...
}

And using it like this :

public class LoginAccountUseCase extends UseCase<String> {
@Override
  protected Observable buildUseCaseObservable(String... params) {
    return mAccountRepository.login(params[0], params[1]);
  }
}
@spirosoik

This comment has been minimized.

spirosoik commented Nov 29, 2015

@MehdiChouag Yes of course I just send you the logic. This is depends in your case but as I said before it will be nice to use domain models to use cases, not plain params.

For example I am passing into the UseCase a UserModel which I am transforming this into User domain model or for credentials you can have a different object for this.

@MehdiChouag

This comment has been minimized.

MehdiChouag commented Nov 29, 2015

@spirosoik Can we avoid to create a UserModel then transforming into a User domain ?
And instead directly use the User domain, that wouldn't break dependency rules.

@spirosoik

This comment has been minimized.

spirosoik commented Nov 29, 2015

@MehdiChouag Yes there is a great discussion (you are in) on this subject and I will support @android10 on this that each layer must have the View Models and each layer should have its model to deal with in order to avoid coupling between layers. It's clear that you are breaking the dependency.

@ghost

This comment has been minimized.

ghost commented Feb 5, 2016

Let me give my 2 cents here.

@MehdiChouag in reality you are not breaking any dependency rule. The domain layer does not use any model from the presentation layer but the opposite. This would leave the dependency as follows:

Presentation -> Domain

which this is correct according to the principle. The idea of having models for each tier it is to simply isolate completely each layer from each other. Also would allow you to have more specific models in the presentation layer without info that might be required in the domain layer. I guess it all boils down to app requirements.

Regarding the UseCase, I used the following solution instead:

/**
 * Interactor to login the user using username and password
 */
public class UserLogin extends UseCase {

    private final UserRepository userRepository;
    private String password;
    private String username;

    public UserLogin(ThreadExecutor threadExecutor,
                     PostExecutionThread postExecutionThread,
                     UserRepository userRepository) {
        super(threadExecutor, postExecutionThread);
        this.userRepository = userRepository;
    }

    /**
     * Initializes the interactor with the username and password to use for the authentication.
     *
     * @param username - username for the user
     * @param password - password for the user.
     */
    public UserLogin init(@NonNull String username, @NonNull  String password) {
        this.username = username;
        this.password = password;

        return this;
    }

    /**
     * Builds the {@link UseCase} observable
     * @return an {@link} Observable that will emit the logged in {@link UserProfile}
     **/
    @Override
    public Observable buildUseCaseObservable() {
        if (this.username == null || this.password == null) {
            throw new IllegalArgumentException("init(username,password) not called, or called with null argument.");
        }

        return Observable.concat(validate(), this.userRepository.user(this.username, this.password));
    }

    private Observable validate() {
        return Observable.create(new Observable.OnSubscribe<Object>() {
            @Override
            public void call(Subscriber<? super Object> subscriber) {
                if (UserLogin.this.username.isEmpty()) {
                    subscriber.onError(new InvalidEmailException());
                } else if (UserLogin.this.password.isEmpty()) {
                    subscriber.onError(new InvalidLoginPasswordException());
                } else {
                    subscriber.onCompleted();
                }
            }
        });
    }
}

So you would execute it like:

this.userLogin.init("myUser", "myPassword").execute(new UserLoginSubscriber()) 
@Fshamri

This comment has been minimized.

Fshamri commented Feb 5, 2016

@jatago, so init must be abstract in UseCase?

@ghost

This comment has been minimized.

ghost commented Feb 5, 2016

In my case I don't actually include it in the base UseCase as abstract method but do it case by case. There might be some cases in which I required different params and some cases in which I don't require any. I just make sure to throw an early exception if any of the required params for this particular UseCase are not set and good to go.

@astelmakh

This comment has been minimized.

astelmakh commented Feb 23, 2016

@jatago what to do with a hard dependency UserLogin in Presentation layer in this case ?

@lutaseb

This comment has been minimized.

lutaseb commented Mar 23, 2016

Sorry to interrupt here but this a matter of architecture, I referenced many issues

  • execute() can take more parameters than just a subscriber witch means buildUseCaseObservable should be overrided as well to pass the defined parameters ...so the base class is almost useless
    btw, injecting UseCase instances when the execute() method doesn't have the same signature is kind of ugly I guess
  • the subscribtion has to be unsubscribed before calling another execute() with other parameters (if the useCase is the same instance of course)
    I think a useCase should should only be reused if a pagination is made, nothing more
  • creating a dagger module seems the "cleanest" way to me but seriously this is overkill
    ....still I don't have any answer but the issue is really serious
@sergio11

This comment has been minimized.

sergio11 commented Sep 5, 2016

It would be a good solution pass bundle to buildUseCaseObservable method?

public class SigninInteractor extends BaseInteractor {

    public final static String EMAIL_PARAM = "email_param";
    public final static String PASSWORD_PARAM = "password_param";

    private FirebaseAuth firebaseAuth;

    public SigninInteractor(FirebaseAuth firebaseAuth) {
        this.firebaseAuth = firebaseAuth;
    }

    @Override
    protected Observable<AuthResult> buildUseCaseObservable(Bundle bundle) {
        if(bundle == null){
            throw new Error("Invalid Params");
        }
        return RxFirebaseAuth.signInWithEmailAndPassword(firebaseAuth, bundle.getString(EMAIL_PARAM), bundle.getString(PASSWORD_PARAM));
    }
}
@roscrazy

This comment has been minimized.

roscrazy commented Sep 6, 2016

Hi all,

I know this thread have closed but I want to share my approach, please leave some comment if possible :
In my approach, I create a factory for the UseCase, and when needed, I will create a new UseCase in Presenter from the Factory.
For example I have this UseCase :

public class DeleteFeedUseCase extends UseCase {

    @NonNull
    FeedRepository mRepository;

    @NonNull
    String mFeedKey;

    @Inject
    public DeleteFeedUseCase(String  feed, FeedRepository repository, ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) {
        super(threadExecutor, postExecutionThread);
        this.mRepository = repository;
        this.mFeedKey = feed;
    }

    @Override
    protected Observable buildUseCaseObservable() {
        return mRepository.deleteFeed(mFeedKey);
    }
}

And this is the Factory :

    public class DeleteFeedUseCaseFactory {
        private FeedRepository mRepository;
        private ThreadExecutor mThreadExecutor;
        private PostExecutionThread mPostExecutionThread;

        @Inject
        public DeleteFeedUseCaseFactory(FeedRepository mRepository, ThreadExecutor mThreadExecutor, PostExecutionThread mPostExecutionThread) {
            this.mRepository = mRepository;
            this.mThreadExecutor = mThreadExecutor;
            this.mPostExecutionThread = mPostExecutionThread;
        }

        public DeleteFeedUseCase create(String feed){
            return new DeleteFeedUseCase(feed, mRepository, mThreadExecutor, mPostExecutionThread);
        }
    }

And how it is used in presenter

void deleteFeed(FeedModel feedModel, int index){
    String key = mKeyStore.get(index);

    DeleteFeedUseCase deleteFeedUseCase = mDeleteFeedUseCaseFactory.create(key);
    deleteFeedUseCase.execute(new DeleteFeedSubscriber());
    unsubscribeOnUnbindView(deleteFeedUseCase);
}

You can check out more detail on my sample https://github.com/roscrazy/Android-RealtimeUpdate-CleanArchitecture

@astelmakh

This comment has been minimized.

astelmakh commented Dec 8, 2016

Try this.

public abstract class UseCase<P extends UseCase.RequestValues, Q extends UseCase.ResponseValues> {
    private P requestValues;

    protected ThreadExecutor threadExecutor;
    protected PostExecutionThread postExecutionThread;

    private Subscription subscription = Subscriptions.empty();

    public UseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) {
        this.threadExecutor = threadExecutor;
        this.postExecutionThread = postExecutionThread;
    }

    protected P getRequestValues() {
        return requestValues;
    }

    protected abstract Observable<Q> buildObservable(P requestValues);

    public final void executeUseCase(DefaultSubscriber<Q> subscriber, P requestValues) {
        this.subscription = checkNotNull(buildObservable(requestValues), "Observable cant be null")
                .compose(applySchedulers())
                .subscribe(subscriber);
    }

    public void unsubscribe() {
        if(!subscription.isUnsubscribed())
           subscription.unsubscribe();
    }

    protected final <T> Observable.Transformer<T, T> applySchedulers() {
        return observable -> observable.subscribeOn(Schedulers.from(threadExecutor))
                .observeOn(postExecutionThread.getScheduler());
    }

    public interface RequestValues {
    }

    public interface ResponseValues {
    }
}
public class GetProfileUseCase extends UseCase<GetProfileUseCase.RequestValues,
        GetProfileUseCase.ResponseValues> {

    private ProfileRepository profileRepository;

    public GetProfileUseCase(ThreadExecutor threadExecutor,
                             PostExecutionThread postExecutionThread,
                             ProfileRepository profileRepository) {
        super(threadExecutor, postExecutionThread);
        this.profileRepository = profileRepository;
    }

    @Override
    protected Observable<ResponseValues> buildObservable(RequestValues requestValues) {
        return profileRepository.get(requestValues.getProfileId())
                .map(profile -> new ResponseValues(profile));
    }

    public static final class RequestValues implements UseCase.RequestValues {
        private final long profileId;

        public RequestValues(long profileId) {
            this.profileId = profileId;
        }

        public long getProfileId() {
            return profileId;
        }
    }

    public static final class ResponseValues implements UseCase.ResponseValues {

        private final Profile profile;

        public ResponseValues(Profile profile) {
            this.profile = profile;
        }

        public Profile getProfile() {
            return profile;
        }
    }
}

@roscrazy

This comment has been minimized.

roscrazy commented Dec 21, 2016

Hi @astelmakh,
I checked your solution. Could it be any problem with the usecase's subscription if we run the "executeUseCase()" multiple time? Example call "executeUseCase" 5 times immediately.

@android10

This comment has been minimized.

Owner

android10 commented Dec 23, 2016

I re-opened this since it was a pending task and due to the lack of time I could not do it.
Thanks for all the input/feedback, here is the commit that allows us to pass dynamic parameters to our use cases: dfd61c2

I will also write a quick post giving the reasons why I opted for that approach.
Feel free to give any other constructive feedback. 😄

@android10

This comment has been minimized.

Owner

android10 commented Dec 24, 2016

I also wrote an article with the arguments behind the design decisions:
http://fernandocejas.com/2016/12/24/clean-architecture-dynamic-parameters-in-use-cases/

As usual, any feedback is very welcome. Merry Christmas! 😄 🎄

@Dharmendra10

This comment has been minimized.

Dharmendra10 commented Feb 10, 2017

I know we have few alternative for the dynamic argument for the UseCase but I want to share my approach. This is similar approach like @roscrazy just little changes.

Here is UseCase

public class GetUserUseCase extends UseCase {

    private final UserRepository userRepository;
    private String userId;

    @Inject
    public GetUserUseCase(UserRepository userRepository, ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) {
        super(threadExecutor, postExecutionThread);
        this.userRepository = userRepository;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    @Override
    public Observable buildUseCaseObservable() {
        return this.userRepository.getUser(userId);
    }
}

Here is the Factory class for the User

@Singleton
public class UserUseCaseFactory {
    @Inject
    public CartUseCaseFactory(UserRepository userRepository, ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) {
        this.userRepository = userRepository;
        this.threadExecutor = threadExecutor;
        this.postExecutionThread = postExecutionThread;
    }

    public UseCase provideGetUserDetailUseCase(String userId) {
        GetUserUseCase mGetUserUseCase = new GetUserUseCase(userRepository, threadExecutor, postExecutionThread);
        mGetUserUseCase.setUserId(userId);
        return mGetUserUseCase;
    }
}

Inside presenter the factory will provide the UseCase

public void loadUser(String userId) {
        getUserUseCase = mUserUseCaseFactory.provideGetUserDetailUseCase(userId);
        getUserUseCase.execute(new GetUserSubscriber());
}
@android10

This comment has been minimized.

Owner

android10 commented Feb 14, 2017

@Dharmendra10 We know there are no silver bullets and sharing experiences is awesome. Thanks

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