I'm creating a chat application using clean architecture, I want to check if the user is logged in when the app starts, and open the login screen if he is not logged in, so my questions are:
What is the best way to implement that? Should I make the LoginActivity the launcher activity and check when the LoginPresenter starts if the user is already logged in then open the MainActivity? And where should I put the logic for checking if the user is authenticated (IsLoggedInUseCase maybe?)?
I don't really understand what is the difference between repositories and usecases, why should I make a GetAllUsersUseCase and EditUserUseCase .. etc, when there is already UsersRepository.getAllUsers() and UsersRepository.editUser(User user)? Why making a whole new class just to reference the the method that already exists in the repository?
Simply, Use-Cases handles your business logic, Repositories are the data layer which you store and access data.
For example when you open the Launcher activity (Let's call it SplashActivity)
First you start your Presenter:
mSplashPresenter.start();
Secondly, in your Presenter's start method you implement a logic if user is logged in or not? if is login navigate to dashboard, if not navigate to LoginActivity.
I assume you have a LoginUseCase.
public void start(){
if(mLoginUseCase.isLoggedIn()){
mView.navitageToDashboard();
} else {
mView.navigateToLogin();
}
}
Third, you need a use case method like the following. (Again i assume you have a UserRepository)
public boolean isLoggedIn(){
// This is your business logic.
return mUserRepository.getCurrentUser() != null;
}
And in your User Repository:
public User getCurrentUser(){
// This is your data
// You can access remote or local data with repository.
return mLocalDataSource.getUser();
}
So why we need a Use-Case?
It's a simple business logic which decides if user logged in or not. This can be a more complicated business logic or you want to use this logic in other presenters. So with Use-Cases, you make your business code re-usable and avoid code duplicate in your presenters.
Later if you want to decide to change your login logic you only change your use-case, not all the presenters.
Let's decide a logic for your question: EditUser.
You have a Repository method UsersRepository.editUser(User user) which edit's the user.
You have a Profile screen which user can edit all fields. Also you have a EditScreenDetail screen that user can edit some of the fields which related by screen details can be seen by other people.
In both screen you edit the user but before call UserRepository method you need to check the required fields which is different by two screens. So you define a ProfileEditUseCase and ScreenDetailsEditUseCase to implement two different business logic. But the final operation is same. You edit the user by your repo. From remote or local.
Summary:
With Use-Cases you separate your business logic from presenters and data layer, avoid code duplicate in your presenters. Also you manage your business which can be used in other parts from one class.
I hope i explained it clearly.
Related
I am following the one-single-activity app pattern advised by Google, so if I want to share data between Fragments I have to share a ViewModel whose owner must be the parent Activity. So, the problem becomes because I want to share data between only two Fragments, independently from the others.
Imagine I have MainFragment, CreateItemFragment and ScanDetailFragment. So, from first one I navigate to CreateItemFragment in which whenever I press a button I navigate to ScanDetailFragment in order to scan a barcode and, in consequence, through a LiveData object inside the ViewModel I can get the scanned value back into the CreateItemFragment once ScandDetailFragment finishes. The problem becomes when I decide to cancel the creation of the item: I go back to the `MainFragment' and because the ViewModel's owner was the Activity's lifecycle, once I go again into CreateItemFragment, the previously scanned value is still there.
Any idea to reset that ViewModel?
but, aren't Viewmodels also aimed to share data between different views?
No. Each viewmodel should be responsible for one view. The "shared viewmodel" pattern is for cases when you have one large view (i.e., activity) that has multiple subviews (i.e., fragments) that need to share data / state, like the master / detail example in the documentation. It's a convenience for these cases when you need real-time updates amongst the subviews.
In your case, you're navigating between fragments and as such should be passing data through the transitions. This means passing arguments along when starting new fragments and registering for results when they complete their task.
Then each of your fragments is isolated, self-contained, more easily testable and you won't end up with a God-ViewModel that does All The Things™ and turns into a giant mess as you try to jump through hoops accounting for every state it could possibly be in.
You can use callbacks in such cases to share data between fragments. or if you use DB/Sharedpreference/Content provider then you do not have to worry about sharing data each page will fetch its own data from the store(DB/SharedPreference/Contentprovider).
you can also try https://medium.com/#lucasnrb/advanced-viewmodels-part-iii-share-a-viewmodel-between-fragments-59c014a3646 if this guide helps
You can clear LiveData value every time when you go into CreateItemFragment from MainFragment.
Or you can just clear it from the CreateItemFragment in onBackPressed() method.
When you cancel the creation of item,set livedata value to null.then within observer code if(updatedvalue!=null) write your code using updated live data value.in this way you can avoid last updated value.
At the moment (on 2022), the method viewmodel.getViewModelStore.clear(); or onCleared(); is deprecated.
So, if you want to clear data holded by ViewModel or clear value of LiveData, you just need use 1 line code like this:
mainViewModel.getLiveData().getValue().clear();
getLiveData() is my method inside MainViewModel class to return liveData variable
getValue() is defaut method provided by LiveData (MutableLiveData: setValue(), postValue())
If you need to clear data when user press on Back button in Fragment, you can do like the code below & put it inside the onViewCreated method - the method of LifecycleFragment.
private void handleOnBackPressed() {
requireActivity().getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) {
#Override
public void handleOnBackPressed() {
Objects.requireNonNull(mainViewModel.getLiveData().getValue()).clear();
requireActivity().finish();
}
});
}
My project on Git if you want to refer code (it still updated): https://github.com/Nghien-Nghien/PokeAPI-Java/blob/master/app/src/main/java/com/example/pokemonapi/fragment/MainFragment.java
I disagree with #dominicoder. At this link, you can find a Codelab made by the Google team updated to Oct 30, 2021. The shared ViewModel pattern can be used when you need a coherent flow to achieve a specific task inside your app.
This method is useful and a good practice because:
The Jetpack team says that has never been a recommended pattern to pass Parcelables. That's because we want to have a single source of truth.
Multiple activities have been heavily discouraged for several years by now (to see more). So even though you're not using Jetpack compose, you still should use a shared ViewModel along with fragments to keep a single source of truth.
Downside:
You need to reset all the data manually. Forgetting to do so will bring bugs into your app, and most of the time, they're difficult to spot.
I am working on a small application using MVP pattern. My home activity has viewpager with multiple fragments in it and each fragment has its own presenter. Fragments aren't communicating with each other so the activity doesn't have any logic, it is just initializing fragments on start.
So if I would like to implement the pattern by the book and stay true to its principals should I implement presenter also for the activity? And if so what should be its role?
If you want to implement MVP by the book and stay true to its principals, every UI that has user interaction should have a presenter. In this case, if your activity is not interacting with the user, there is no need to have a presenter, and your fragments can have their own. If your activity needs, let's say show a loading to the user because of some data loading prior to show the fragments (this is a user interaction because you are interacting with the user to let them know that something is happening so they should wait), then might be good to consider having a presenter for the activity.
MVP doesn't care at all about whether is an Activity/Fragment/View, it just knows View which is considered as an abstraction of whatever can be shown to the user :)
That is at least, from the 'rules' perspective. My 2 cents is, be flexible, if you see that it actually ends up adding value to you and your project, do it, otherwise, sometimes you have to 'break' the rules or create your own.
For using the fragments with their own presenters, I try to use the presenter-contract classes duo to manage the UI events in the fragments.
For example, Consider a click event to show a toast message in case of two possible outcomes: 1. Save and 2. Delete
Then, I will declare two view contract methods like this:
interface View{
fun showSaveMessage()
fun showDeleteMessage()
}
And then, in the fragment, I will use an instance of my presenter class to display the messages at appropriate times like: presenter.doSaveAction(), the presenter in turn will cause the view to show the toast message.
Also, when I come to the actual logic of the fragment, like for fetching some data from a remote server, I use Interactor class along with the Presenter-View classes to perform it.
I believe staying true to all the principles is virtually dependent on what kind of application you are building. Sometimes, it is more feasible to use MVVM with MVP than only MVP pattern for the app architecture too.
I hope this answers your question, kind of?
I have an Android Activity that I'm using Dagger2 to inject a Presenter into. I'd like my Presenter to be capable of holding state even if a configuration change occurs.
For instance, I'm going to use the Presenter to kick off a network call and if the user rotates the device while the network call is in-flight I'd like to be able to receive the response after the device finishes its rotation and not have to restart the call.
I'm getting tripped up because if I scope the instance of Presenter to the Activity's life, then isn't there a chance that the Presenter would be garbage collected when the Activity goes through onDestroy() during a configuration change? My other thought was to use a scope that is valid during the life of the application. However, if I do that how do I ensure that my Presenter can be garbage collected once the Activity has been destroyed for good (not due to a config. change, but something like the back button being pressed)?
Is there a way to ensure that my Presenter will survive an Activity's configuration change and also not be leaked for the life of the Application?
I would strongly advice against trying to implement this approach.
You're effectively trying to use DI framework in order to support Activity specific life-cycle flow, although DI frameworks are not intended to be used like this.
I recently answered another similar question in which OP tried to share state in View-Model between different Activities. Although use cases are not identical, the general pattern is the same - attempt to delegate flow control responsibilities to DI framework, which is not a good idea.
The best approach in your case (IMHO) would be to store the current state before rotation, re-instantiate the presenter upon rotation, and then restore its state.
How you store the state during rotation depends on what exactly you're trying to preserve:
If you need to preserve UI related state (selections, texts, position of elements, etc.) then you can use the usual onSaveInstanceState() and onRestoreInstanceState() callbacks
If you need to preserve some business related state (ongoing network requests, data, data modifications, etc.) then encapsulate this logic in a business class (e.g. SomeBusinessUseCaseManager) and inject this class from Application wide component with a scope.
You can find a detailed review of Dagger's scopes here.
More information about DI in Android can be found here.
According to this article about Custom Scopes:
http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/
In short - scopes give us “local singletons” which live as long as scope itself.
Just to be clear - there are no #ActivityScope or #ApplicationScope annotations provided by default in Dagger 2. It’s just most common usage of custom scopes. Only #Singleton scope is available by default (provided by Java itself), and the point is using a scope is not enough(!) and you have to take care of component that contains that scope. This mean keeping a reference to it inside Application class and reuse it when Activity changes.
public class GithubClientApplication extends Application {
private AppComponent appComponent;
private UserComponent userComponent;
//...
public UserComponent createUserComponent(User user) {
userComponent = appComponent.plus(new UserModule(user));
return userComponent;
}
public void releaseUserComponent() {
userComponent = null;
}
//...
}
You can take a look at this sample project:
http://github.com/mmirhoseini/marvel
and this article:
https://hackernoon.com/yet-another-mvp-article-part-1-lets-get-to-know-the-project-d3fd553b3e21
to get more familiar with MVP and learn how dagger scope works.
How to apply Firebase Analytics(for example) on MVP app architecture? (I use Mosby to build MVP)
I want to track events of "opening screen", "do click action".
There is how I send "opening screen" event.
private const val ANALYTICS_SCREEN_NAME = "ask_password"
private const val ANALYTICS_ACTION_DONE = "done"
class AskPasswordPresenter : MyDiaryPresenter<AskPasswordView> {
#Inject
constructor(analytics: AnalyticsManager) : super(analytics) // AnalyticsManager is wrapper around Firebase Analytics API
override fun initialize() { // this method called when new ViewState created
super.initialize()
analytics.doScreenOpened(ANALYTICS_SCREEN_NAME)
}
fun done(password: String) { // called when user click on 'Done' button
...
analytics.doAction(ANALYTICS_SCREEN_NAME, ANALYTICS_ACTION_DONE)
}
}
doAction(...) called as it must. Okay.
initialize() called even when user navigates back to the screen from backstack. I want it to send event ONLY when user navigates to screen in "front direction". It also looks like a bad solution as initialize() method introduced for initializing Presenter when ViewState was created at the first time, not for logging analytics events.
It sounds like I must share Fragment's lifecycle to Presenter. Not good.
What can you recommend? Must I create another entity, like AnalyticsPresenter for each Fragment? How do you handle this case?
In my opinion Analytics belongs to the View layer and not Presenter layer.
So track it either directly in Fragment / Activity or (what I usually do) use one of the libraries like lightcycle
or CompositeAndroid to kind of plug in a "Analytics component" to your Activity / Fragment. By doing so your Fragment / Activity doesn't contain the code for Analytics but is rather decoupled into its own class (single responsibility).
I think analytics belong to presenter, but as i answered on similar question having analytics in View it's easier to jump on button/labels/... definitions and see where this button is located in UI and have better idea what to send for Category, Actio, Label and Value param of GAnalytics. I think i don't need to mention presenter mustn't have any android specific dependecies so you can't jump to button/labels/... definitions from presenter. Regards
I've been looking at Custom Scopes in Dagger 2 (this and this are the ones i'm trying to base my code in), and there's one thing that i still can't seem to understand, i see that you can create a component with a custom scope and then all the provides form the modules contained by that component will be either the same scope as the component (singleton in the component) or un-scoped which will return new instances every time you get one.
But, the thing i still don't get is, if you have a User scope, and then you have some modules tied to that scope, let say that your network component is tied to it, so that the network calls use the current user information, if you sign-out the user (or sign in the user) mid Activity life cycle, will it change the object references that you currently have marked as #Inject? or whatever instance you got when you called .inject(this) in the activity onCreate method?
Or you should call inject one more time in order to get the references mapped again?
Any help on this matter is highly appreciated :)
tl;dr You have to manage everything yourself. There is no refresh, you have to recreate or at least reload parts of your activity.
Scopes provide some compile time information and help you to keep your code "readable". To actually swap components, this you will have to do yourself. And yes, you have to build your design around this, that depending components get recreated accordingly.
If the user logs in / out you will have to create a new UserModule and component referencing the new user, supplying logged in / out objects. This is the new component you need to reference for all future components depending on it.
#Inject annotated fields will not refresh automatically, albeit you can just inject a second time to the same fields, the objects will just be overwritten.
In the second link you provided they actually do implement some sort of swapping the user information. This is done by keeping the UserComponent in the application class.