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
Comments
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). |
@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):
@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);
}
}
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? |
@android10 please when you have free time it will be nice to give us a better approach by your side. |
@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 In my /**
* 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 @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:
in order to use the attributes into the UseCase. Any suggestion for a different approach? |
@spirosoik Does the solution example in your last comment works nice with your needs ? |
@MehdiChouag It's easier to keep the abstraction as it is with
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 I am suggesting the first solution. @android10 @lalongooo if you have a better solution than the first one, I would love to listen. |
@spirosoik For me the first solution seems to be more appropriate. Rather using 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]);
}
} |
@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. |
@spirosoik Can we avoid to create a UserModel then transforming into a User domain ? |
@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. |
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()) |
@jatago, so init must be abstract in UseCase? |
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. |
@jatago what to do with a hard dependency UserLogin in Presentation layer in this case ? |
Sorry to interrupt here but this a matter of architecture, I referenced many issues
|
It would be a good solution pass bundle to buildUseCaseObservable method?
|
Hi all, I know this thread have closed but I want to share my approach, please leave some comment if possible :
And this is the Factory :
And how it is used in presenter
You can check out more detail on my sample https://github.com/roscrazy/Android-RealtimeUpdate-CleanArchitecture |
Try this.
|
Hi @astelmakh, |
I re-opened this since it was a pending task and due to the lack of time I could not do it. I will also write a quick post giving the reasons why I opted for that approach. |
I also wrote an article with the arguments behind the design decisions: As usual, any feedback is very welcome. Merry Christmas! 😄 🎄 |
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
Here is the Factory class for the User
Inside presenter the factory will provide the UseCase
|
@Dharmendra10 We know there are no silver bullets and sharing experiences is awesome. Thanks |
Kotlin here. create an interface (here :
Change your base
According the changes in the
If your usecase doesnot take in any arguments then you can use this.
|
What's the point of having a UseCase interface/base class? What's wrong with a plain class like this:
|
I use a plain approach like the one you wrote here. It is flexible and works very well for me. But this is mainly because my use cases do not use shared code, which should be placed in a super class. But I guess in really big projects with several developers a more strict approach (with a base class or an interface) can be useful, but I have not encountered this necessity yet. |
As in the example, usecase's parameters are set via
UserModule
: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 toUserModule
. With this approach if the view is aFragment
then I have to re-createUserModule
again in theFragment
.Any suggestion, recommendation?
The text was updated successfully, but these errors were encountered: