I faced with that problem when I started to use ViewPager. As every page is nested Fragment, I can't call setRetainInstance(true) for it. So, I need to store Fragment's state to a Bundle and cancel/recall remote API methods onViewAttached/Detached which I don't want to.
What I learn about this situation:
I can use RecyclerViewPager to avoid using nested Fragments, but I still can't keep an instance of Presenter. One of the ways is to use a static field, but the same thing can I do with the Fragment.
Create some kind of rootViewPager under MainActivity and use it in Fragments via setVisibility(GONE/VISIBLE) and replacing Adapter. So, every Fragment placed into this ViewPager will not have parent Fragment and I will solve my case. Suitable and elegant, but not the best solution as I think.
Any other variants?
In Mosby 3.0 Presenters can be retained even without setRetainInstance(true) ... I would suggest to wait until 3.0 release ...
So, I need to store Fragment's state to a Bundle and cancel/recall
remote API methods onViewAttached/Detached which I don't want to.
Mosby 2.0 does exactly that for you but you have to make your ViewState and your data implement Parcelable. In that case the presenter instance won't survive screen orientation changes, but presenter will "resume" on the same state / point (a new presenter instance will be created, async tasks etc. might be restarted too). See RestorableViewState (javadoc is slightly outdated, because it mentions that this is the only way to work with activities which since Mosby 2.0 is not true anymore)
Related
Scenario 1 - If we use ViewModels to communicate between fragments, then the ViewModel has to be created by activity reference and hence going to stay there in memory until the activity is destroyed.
Scenario 2 - In master-detail flow ViewModel makes our life easier but again the memory usage issue is there.
Scenario 3 - We have viewModelScope in the new version of arch library to cancel jobs with Fragment/Activity lifecycles, but if ViewModel is created with activity reference then it's going to stay there until activity is destroyed. Hence the job can still be executing and fragment is already gone.
You can use ViewModels to communication between two different fragments(aka SharedViewmodels) because it's simple but it's not perfect.
As you know the SharedViewModels must be alive until the first joint parent (activity or fragment) is alive.
but wait ...
What is the purpose of ViewModels?
Are we create ViewModels just for communication between fragments? Absolutely not.
Is the use of ViewModels against the purpose of ViewModels? no, I say it's not perfect use them for communication between fragments but if you have a small project you can use them because it's simple.
So what can I do instead of using ViewModels for communication between fragments? You can build independent UI components and use them for communication between fragments.
What is the advantage of building this weird independent UI components? In this way, each component has its own ViewModel (or Presenter) and they haven't any parent/child relation; Instead, they updated from the same business logic or in reactive programming they are just observing the same Model.
What is the meaning of building independent UI components and how to build them? if you are already familiar with reactive programming, I recommend reading this amazing blog post by Hannes Dorfmann; If you don't, I simply propose using EventBus library for communication between fragments but You will soon realize that too much usage of this library leads to spaghetti code.
Scenarios
If the fragments are not part of a flow/group, then don't share the ViewModel, just pass some id/data to the new fragment, create its own viewmodel, and query the data for the fragment from its own viewmodel.
If the fragments are part of some flow/group (cart/checkout/booking flow, multi-screen registration process, viewpager fragments, etc) and the logic is common enough, then share the viewmodels between the fragments. In single-activity architecture, I put these flow/process in its own root parent fragment that acts as a host and is used to create the scope of the viewmodel. For example:
MainActivity ->
-> RootAuthFragment
-> SplashFragment (replace with below)
-> LoginFragment (add to backstack with below or onsuccess login go to MainFragment)
-> SignupFragment (onsuccess go to Main)
-> MainFragment (replace with RootAuthFragment)
In the above scenario, you can share the viewmodel between login and signup screens with RootAuthFragment's scope. If you have a multi-screen signup process, then you could move that into separate root fragment and create a separate viewmodel for the signup flow.
Bundle vs ViewModels:
Bundles are used to pass some values. So, use it just for that. I use bundles to usually pass primitive data types or enums and based on that I query the actual data from the viewmodel (through android room or retrofit) or if the data objects are small enough, I make them parcelable and just pass that.
If you have a shared ViewModel and it's becoming a god class and does a lot of different things, then it means those fragments need separate ViewModels. Don't share the ViewModel just for data. Share the ViewModel for the common/shared behaviour/data/logic (whichever makes sense for your particular use cases)
I prefer you should use View Models approach if you are using single activity architecture. To justify my answer I will clear your scenarios here.
Scenario 1 - If we use ViewModels to communicate between fragments, then the ViewModel has to be created by activity reference and hence going to stay there in memory until the activity is destroyed.
Scenario 2 - In master-detail flow ViewModel makes our life easier but again the memory usage issue is there.
As for memory you are already holding information into memory there is no escaping there. If you don't need data for stay there then you can clear data from models also but again it will kill the purpose of storing data in the first place.
If you pass data using bundle it's also going to take memory there also.
Scenario 3 - We have viewModelScope in the new version of arch library to cancel jobs with Fragment/Activity lifecycles, but if ViewModel is created with activity reference then it's going to stay there until activity is destroyed. Hence the job can still be executing and fragment is already gone.
That's the main purpose of using view models it will store the last state for user where he left.
As per https://developer.android.com/topic/libraries/architecture/viewmodel states
This approach offers the following benefits:
The activity does not need to do anything, or know anything about this communication.
Fragments don't need to know about each other besides the SharedViewModel contract. If one of the fragments disappears, the other one keeps working as usual.
Each fragment has its own lifecycle, and is not affected by the lifecycle of the other one. If one fragment replaces the other one, the UI continues to work without any problems.
I am somewhat new to Android, and I am writing an app. I am getting to the point where I am starting to more thoroughly test my code, and therefore, I would like to implement the MVP design strategy since it adds more testable layers to the code. One of the supposed benefits of using MVP that I can not seem to understand is how it helps with running AsyncTasks as they are performed dynamically. Since you want to avoid any Android specific components in your Presenter class, how are you supposed to reference the Activity that utilizes the AsyncTasks? Tutorials about MVP show the Presenter object having methods that take in an Activity as a parameter and return to it; however, if your AsyncTask takes a long time and your Activity has been destroyed through something such as rotation change, how do you return to the proper Activity? I currently store my AsyncTask in a Fragment so that it is saved on Orientation Change. I am having a hard time finding a workaround that implements the MVP practice.
To answer your question, there isn't much you can do to avoid passing Android classes to your Presenter class. But instead of passing the Android object as a parameter, add a method to your View class that returns it (e.g. getActivity()).
That said, I strongly suggest you use a Loader instead of an AsyncTask. Loaders were designed specifically for your use-case. They can also run in the background but their lifecycle is tied to the lifecycle of an Activity or Fragment.
If you switch to Loaders, add a method like getLoaderManager() to your View interface.
If I dont miss-understand your question, your are trying to use retained non-UI fragment for long-runing task, right?
Here is my suggestions in your case:
Make ActivityView interface for your Activity
Using WeakReference<ActivityView> to refer your activity inside your Fragment Presenter (to avoid memory leak issue)
When Activity re-created, try to get your retained fragment and reset your Fragment Presenter's ActivityView. You can look at this Google Example to know how to deal with loading data while configuration changed.
In conclusion, just use WeakReference to avoid memory leak issue, and try to re-set your Presenter'sview when activity is recreated
Probably the answer is "you can't", because in Mosby 2.0 you need to use fragment and set it to setRetainInstance(true) to preserve Presenter. And the aim of Conductor is to remove the need of using Fragments, so there is no way to use setRetainInstance(true) anywhere in your app.
But maybe there IS another way..
Here is the official Mosby plugin for Conductor:
https://github.com/sockeqwe/mosby-conductor
Sorry i cannot comment because i don't have enough reputation but Conductor is using Conductor.attachRouter in order to take instance inside an activity and attachRouter is using the LifecycleHandler in order to take a "saved" (aka retained instance) because LifecycleHandler is a headless fragment. So in order for you to have a presenter which is not being destroyed on configuration changes all you have to do it to create your presenter inside the constructor of the controller. If i have understand right, the constructor of the controller has the life time of a headless fragment. So problem solved or i might have made a mistake somewhere...
Firstly, I know that with Model View Presenter there are different implementations, and in my mind as long as you have the layers of abstraction clearly defined and doing their appointed roles then how you implement this pattern is open to interpretation. I have been implementing this pattern in quite a few apps where there was just one Activity. I've now started a new project that has multiple Activities and attached Fragments, including nested fragments (ViewPager).
I'm now trying to translate the MVP to this project and I've hit a concept wall and would like some guidance and insights.
So far I've created the above structure and started to do a 1 : 1 relationship with View & Presenter (regardless of Activity or Fragment). I feel that this is OK, however if for example I sent a request to do something from an Activity View to its Presenter, which returns a result to the Activity View how would I go about propagating the result i.e. update all the other Activities/Fragments that are currently not in a Paused() or Stop() state. I feel like in this case there should be a central Presenter that updates all necessary Activity and Fragment Views, but I'm not sure how to go about doing this.
Currently when each Activity and Fragment is created it creates a new instance of a Presenter class, passing in itself as a reference (the Activities and Fragments implement their own interfaces), which the presenter stores as a WeakReference and can invoke the relevant interface methods when returning a result.
According to the docs whenever Fragments want to communicate with one another and the attached Activity you should use a callback interface. With this in mind should I have one callback interface that the Activity implements and the Fragments callback to whenever they request something, so in essence only the Activity would have a Presenter and Model layer that the Fragments have to callback to in order to make various requests?
Sorry if this sounds a bit muddled, hopefully this is clear enough to understand what I want to achieve, and if I’m thinking along the right lines... or totally off the mark!
I think this is okay to have a presenter inside activity. Basically activity is like a controller, it should know about the presenter.
It would be wrong to put a presenter inside a fragment if activity or other fragment needs it too. You can put a presenter inside a fragment only if this presenter is designed specifically for fragment.
which the presenter stores as a WeakReference and can invoke the relevant interface methods when returning a result
Why do you need a WeakReference here? If you have 1:1 relationship then I assume your presenter does not have it's own lifecycle, meaning that it's lifecycle depends on either activity or fragment. There is no risk of having memory leaks because it's not a singleton, right? It should be safe to have a strong reference.
I'm not sure if I answered your question because it looks a bit broad to me. My point is that, fragments are just separated "parts" of activity and you should treat them as parts. If presenter belongs to this part only, then it should be inside. Otherwise it should be in activity. You are right about using an interface to access activity, this is a well-known design approach which Google uses in their examples.
Nope, no interface anymore. You either use RxJava Observables to update all the views as described here or some kind of Bus(Otto-deprecated or EventBus). And you will like it because they make interacting too easy.
I'm trying to access a function in one Fragment (f1) from another Fragment (f2). It seems that i have to make my function in f1 public and static to be able to access it from f2.
I've read that it's not a good idea to access one Fragment from another, so i've tried to make f2 access my Activity, which then connects to the function in f1. Although, even doing it this way, i still have to make my function in f1 public and static.
I don't reuse the Fragments, i simply have them in a ViewPager for swiping back and forth through the Fragments.
At the moment i have a lot of static variables because i have to make my functions static to access them from other Fragments.
Am i going about this the wrong way? Is there a better way?
Thanks in advance.
You're going down a road of much pain unless you are very controlled about what you do.
First of all, sharing state through static methods and variables is a fairly awful way of doing things, and static members won't be able to access anything in the instance of the fragment. If you really think you need to use static methods, don't bother putting them in the fragment classes. They don't provide an advantage there. Put them in a common class that they all reference.
Second of all, if you're using a ViewPager with fragments, you can't be guaranteed that any given fragment's view hierarchy even exists at any moment in time. This is because ViewPager typically only keeps fragment views alive that are on the current page or on +/- 1 offset from the visible page. So if you need to tell a fragment at offset +2 from the current fragment, you won't be able to make visible changes to it.
Instead, the easiest thing to do is create an object that maintains whatever state you want to share between the fragments, and have them all make changes to that one object. If you need instant changes to other fragments, you can use something like an event bus to have those changes communicated to other active fragments.
What you're trying to do is in general fairly complex. Expect to spend a lot of time designing a correct solution, and be sure to learn how fragments and ViewPager works very well.
You got two questions
1. Am i going about this the wrong way?
Yes, its not recommended to have the methods & variables declared static just to make them used to access from outside of your class.
2. Is there a better way?
Yes, declare the methods as public (but not static) inside its fragments.
From the activity (which is hosting the fragments) get the reference to the fragments using FragmentManager classes methods findFragmentById() or findFragmentByTag() then call the methods.
Sample :
Fragment fragment =
fragmentManager.findFragmentByTag.findFragmentById
(R.id.fragment);
or
Fragment currentFragment =
fragmentManager.findFragmentByTag("fragmentTag");
If you really require both of your functions to be isolated, you can use Broadcasts. Just send a Broadcast from one fragment and have the Broadcast receiver in the other fragment.