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
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.
this one question has been bothering me for 6 months, it is like a missing peace.. So, I really like LiveData and use it a lot, perhaps too much. Basically, all our fragments adding and removing is managed by LiveData. I have done it for several reasons:
We need to remove fragments in some cases, after onPause has occurred (odd, but a must for our use case).
We have only a single activity with fragments.
I have created a specific navigationViewModel which is shared across all fragments and is created in activity.
I add, remove fragments in this manner:
//ViewModel
...
val addFragmentNr3 = SingleLiveEvent<Boolean>()
//Activity or some fragment calls this:
navigationViewModel.addFragmentNr3.value = true
Then I observe LiveData in Activity and handling transition:
navigationViewModel.addFragmentNr3.observe(this, Observer { response ->
if (response != null) {
if (response) {
router.addFragmentNr3(supportFragmentManager)
}
}
})
Then router handles it:
fun addFragmentNr3(supportFragmentManager: FragmentManager) {
val fragmentNr3 = FragmentNr3()
supportFragmentManager.beginTransaction().replace(R.id.root_layout, fragmentNr3, FRAGMENT_NR_3.commit()}
In my honest opinion this should definitely prevent from this crash:java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
However, it does occur in our crash analytics.. It occurs rarely after more complex logic (like updating livedata after onActivityResult), but it does occur...
My main question is: Isn't it is a case, that LiveData handles such scenarios and would emit results only when it safe to perform operations? If not, it means my logic is bad and this approach is complete failure.
P.S. I would like to use navigation library, but as I said we have to manually remove some fragments after user goes to background, or uses split mode and etc.
LiveData does not know whether an action is safe to perform or not.
onSaveInstanceState() is called sometime before onStop() for Android version below P. So there is a small chance that the observer gets notified after onSaveInstanceState() is called.
According to doc, it turned out that onSaveInstanceState() should mark the lifecycle as CREATED and observers are not supposed to be called after onSaveInstanceState().
Suggestion on how to fix it.
One way is to use Android Navigation component and let it handle all of the fragment transaction.
If this is not feasible--like op's case--I suggests just using .commitAllowingStateLoss().
fun addFragmentNr3(supportFragmentManager: FragmentManager) {
val fragmentNr3 = FragmentNr3()
supportFragmentManager.beginTransaction().replace(R.id.root_layout, fragmentNr3, FRAGMENT_NR_3
.commitAllowingStateLoss()}
Now, if you search on the internet there will be dozens of articles warning how using .commitAllowingStateLoss() is bad. I believe these claims are no longer applicable to modern Android development where view restoration does not rely on saved bundles. If you are building an Android application with view models, you hardly need to rely on the Android framework to do the saving. In a proper MVVM application, the view should be designed in a way that it can restore its complete state based on its view models, and view models only.
I've been using MVP for a long time now and I'm starting to transfer to a hybrid state between MVP and MVVM
In detail my apps will go like this:
Each Activity has a 0 to x Fragments that represent its views
Each Fragment will request the Activity's ViewModel so that they can retrieve data using LiveData
The Activity will have a seperate ViewModel which will act as the presenter. On creation that ViewModel will be injected with the Activity's ViewModel with the LiveData so that it can update the UI as needed
The presenter will get the messages sent to the data ViewModel and send the results back to it
My questions:
Could holding a reference to the data ViewModel in the presenter ViewModel cause a memory leak or adverse effects such as memory leaks?
Where should business logic be? in the presenter or in the model part?
For example, let's say I have a list of items and the user long presses one to edit them, what part of this architecture should be responsible for checking if the user has permission to do this and either let them edit the item or show an error message?
Is there a way for the Fragments to only get part of the Activity's ViewModel?
For example , assuming the activity has 3 Fragments under it, and one ViewModel to cater to them
Can I use something like:
class MainViewModel : ViewModel() , IFragmentA, IFragmentB, IFragmentC
and then when I try to get the ViewModel in the fragments I can write something like:
lateinit var viewModel: IFragmentA
override fun onAttach(context: Context?) {
super.onAttach(context)
vm = ViewModelProviders.of(context as AppCompatActivity).get(IFragmentA::class.java)
}
note:I know the above code does not work , what I am asking is if there is a way for something similar to this could work
Is the correct way to send back messages to the activity SingleEvents?
For example, if the user tries to delete an entry , and I wish for them to enter a password, would the flow be:
The Fragment sends the message to delete to its ViewModel
The ViewModel passes it on to the Presenter
The Presenter decides that it needs password verification before moving on
The presenter sets the value of a SingleEvent in ViewModel
The ViewModel notifies the event's subscribers (in this case the MainActivity) that they should show a dialog asking for a password
Thank you for any help you can provide
I have recently ported one of my app from MVP to MVVM architecture. it doesn't matter whether you do it partially or completely, you are moving towards something great and clean and you are going to like it.
Before checking the answer please have a look at this MVVM architecture diagram and some of it's dos and don'ts
Let's look at the roles of each classes here.
Activity/Fragment:
-Listen to MutableLiveData Obeservers and set Data to the views, no other logics here.
ViewModel
user input Validations (username, password empty or null checks)
set your mutableLive
ask repository to start a task network or localdatastorage(sqlite), with callbacks.
Repository
cache required data.
should not hold any reference to ViewModel this will create a circular dependency.
Decides what to do - whether to make network call or load data from local storage. manipulation of the data received goes here(business logic).
use the callback received from ViewModel to update data to viewModel, strictly no direct communication.
RemoteDataSource
makes a network call and gives the data received back to the repository.
LocalDataSource
handles all SQLite related stuff and gives the requested data through callbacks.
there is a todo app sample project from google which uses MVVM. please refer it, it will be very helpful.
No presenter - check user inputs on viewmodel and communicate forth using repository and back using MutableLiveData.
Do your business logic in Repository, consider it more like a model in mvp pattern.
You can have single viewModel for your activity and its fragments. All your fragments communicate through one viewModel. So Each Fragment will only react to the LiveDataObserver it listens to.
There is actually an example of this use case in the Google sample project for MVVM.
AddEditTaskActivity.java
public static AddEditTaskViewModel obtainViewModel(FragmentActivity activity) {
// Use a Factory to inject dependencies into the ViewModel
ViewModelFactoryfactory= ViewModelFactory.getInstance(activity.getApplication());
return ViewModelProviders.of(activity, factory).get(AddEditTaskViewModel.class);
}
AddEditTaskFragment.java
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View root = inflater.inflate(R.layout.addtask_frag, container, false);
if (mViewDataBinding == null) {
mViewDataBinding = AddtaskFragBinding.bind(root);
}
mViewModel = AddEditTaskActivity.obtainViewModel(getActivity());
mViewDataBinding.setViewmodel(mViewModel);
mViewDataBinding.setLifecycleOwner(getActivity());
setHasOptionsMenu(true);
setRetainInstance(false);
return mViewDataBinding.getRoot();
}
Password Verification Flow:
fragment ask the ViewModel to deleteEntry.
Ask repository to decide whether verification is necessary, with the data which we already have or communicating with the local data source.
ViewModel receives a callback from Repository saying verification needed, ViewModel updates the respective MutableLiveData showVerification.postValue(true);
As the activity is listening to the showVerificationObserver, it shows the verification UI.
Hope it helps.
I am working on an application where there is a log in form.I am bit confused with the pattern as I don't understand as how I will open the new activity as my login is successful.As per my understanding when I click on submit button a method in viewmodel which authenticates will get call and after my successful login I do not know how to navigate it to activity file so that I can call new activity.
Thumb Rule:
No package from android.* should lie in ViewModel. You can ignore package import for ViewModel
Also, you can do it with normal ViewModel as well.
How to proceed?
Lets make it simple. Suppose, you are making Login page.
Lets have below things in ViewModel itself:
Fields of email & password with two-way binding
Form Validation logic
Button Click Event
Api Call
All these things lie in your ViewModel.
Now, your Activity needs to react to the result of your Api Call. So, let your ViewModel have a separate LiveData where T is the type of Response from your Api Call.
For example:
val loginApiStatus = MutableLiveData<LoginResponse>()
And then, let your Activity observe this LiveData. It should be observed in onStart() method of Activity. I will tell you the reason why to observe in onStart().
viewModel.loginApiStatus.observe(this, Observer{ loginResponse->
// respond accordingly
})
Now, once you receive response from Api, simply update the LiveData in your ViewModel as:
loginApiStatus.value = loginResponse // Login Api Response
With this structure, you have total control over handling the Api Response. Even if, your activity goes into background, after launching Api Call, you will still have the state of what happened to that Api call. Now, when you return to Login screen again from background, you start observing the LiveData again (because we are observing the state LiveData in onStart() as I said earlier), and you will get the state to react upon.
Life becomes a lot easier when you start storing states of your View / Fragment / Activity, in your ViewModel itself.
for that, you can use AndroidViewModel which gives Application Context and then using intent you can navigate to the new activity.
You can simply implement a click listener in activity and handle opening new activity from there. As far as I know, ViewModel should only be used to persist data or do other communication with Repository/model. It should not be used for navigation.
Although if you want to use then you can use AndroidViewModel class which provides a context object that can be used for navigating to another activity/fragment.
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.