MyApp need hold a User object in whole context,A,B,C activities'xml use this User object,when A edit User,I want B and C notifyChange,how to deal this problem with databinding,livedata and viewModel?
Formerly I make User.class extend BaseObservable,but POJO will be very troublesome and must not be a null,sometimes User maybe null such as not login.
Now I Change to use LiveData, make Pojo simple and not extend BaseObservable,but when A edit,B and C not work,I think i need ABC use same viewModel instance in memory,but this will cause viewModel's onClear() trigger manytimes.
Another way is to have one singleton repository to hold your user data and each viewModel can have that repository and share the same data between activities.
Based on this part of the documentation:
https://developer.android.com/topic/libraries/architecture/livedata#extend_livedata
The fact that LiveData objects are lifecycle-aware means that you can share them between multiple activities, fragments, and services. To keep the example simple, you can implement the LiveData class as a singleton as follows:
You can create a singleton for your view model like I did here:
companion object{
private lateinit var instance: ViewModelProfile
#MainThread
fun getInstance(userId: String): ViewModelProfile{
instance = if(::instance.isInitialized) instance else ViewModelProfile(userId)
return instance
}
}
Then I call it and get instance anywhere like this:
val profileVModel = ViewModelProfile.getInstance(user.uid)
If you want to share common ViewModel between ABC activities, then it is suggested to keep them as 3 fragments in a single Activity, create ViewModel of that Activity which can be shared among all three fragments A, B, and C.
Also what you are trying to achieve with activities is like this, suppose you have done some operation in activity A, if you want Activity B and C to get notified about them then they need to be running to get notified, which won't be happening, so instead you should use Intent or Bundle to pass needed information when the activity get started.
Updated
There are other ways as well to achieve similar kind of functionality like,
Event Bus
RxJava Subjects (refer this)
Android State by Evernote (refer this)
This will allow you to have application level access of state, which can be accessed by any Activity or Fragment
Related
I'm new to android, I wanted to know if it is okay to access properties initialized in activity / call activity functions from fragment like this or is it bad practice and I should avoid it.
(requireContext() as BaseActivity).viewModel
(requireContext() as BaseActivity).countryList
(requireContext() as BaseActivity).getSomething()
Your instincts can be right. Breaking changes can be caused by name conflicts, variable shadowing, wrong imports, wrong assignment to values. But these days, the demand for features is increasing, in such that you need the public accessor. Just have this rules in your conscience:
Interfaces are powerful at class to class communication
Inherit what is important, override what is implemented already, pass to param to lessen global var damage
If a variable can be stored in another global form, consider it with regard to size(ram matters), speed of access(ux matters), security(keys matter) and volatility(nulls matter).
Now looking at your code, I can see you have a fragment system that is based on values/functions stored in the main activity, that provides the context for the fragment. If you apply the first point: Your fragment will implement a BaseFragment that already some context cast i.e. lateinit var mainActivity: MainActivity then you can mainActivity.viewModel anywhere in your fragment without casting. And this is cleaner
Applying the second point: in the BaseFragment (that will be inherited by AnotherFragment)
abstract var viewModel: ViewModel
abstract fun initList()
open var countryList = mutableListOf()
open fun onScale(detector: ScaleGestureDetector) { //pinch: increase visible country list like some nice zoom effect .. etc }
if most or all of your fragments need similar functions or variables, make abstract to something you can forget will crash the app, make open for those 'features but I dont need to rewrite so I'll call super.function' functions. Make a var open if some super function overrides it, and just put var if you seriously dont know when you want it and when to change it.
On the third point, Android in the early stages, we learnt the hard way that context doesn't last forever even if your app is running. Rotation and lifecycle functions will swap it rough and fast. So consider other storage ways. I still dont trust requireContext/Activity/view for context, so cast with caution.
A big NO. It is a bad practice to use hardcode references to activity from fragments.
I see that you are using viewmodel, which indicates that you are using MVVM, you should use Sharing data between Activities and Fragments in MVVM made simple with SharedViewModel concept for communicating to viewmodel of the activity.
For communicating to the Activity which hosts your Fragment you should use an interface pattern of communication. from fragment to the activity
Let's say one of your activity won't extend the base activity as it has to extend one activity from a library lets say YouTubeBaseActivity, and it will host a particular fragment now the cast to BaseActivity will never succeed in your activity.
I'm learning Android ViewModel now.
and I have MainActivity and RegisterActivity which can type name, age, and phone number in there.
after typing that information in the RegisterActivity, I put that info into a ViewModel and click a button and finish the RegisterActivity.
the thing is, I want to show the info in MainActivity.
so how can I share the viewModel between the two activities?
what a concept do I have to use?
You can't share a ViewModel across Activities. That's specifically one of the downsides of using multiple activities as per the Single Activity talk.
You can put the data you want to keep for the MainActivity into your Application class or use SharedPreferences.
viewModel cannot be directly shared between activities however, if u store viewmodelfactory in application class and retrieve factory object from activities and access viewmodel then u can share state of view model, i checked this senario in featureModule app, it worked
ViewModel has application level lifecycle. If you use single viewmodel with two or more activities then after attaching with first activity when it get attached with other one. then there might be some problem.
Passing data between Fragments in Navigation Component is easy. Say going from A to B you just set arguments with SafeArgs and you are done.
But, it gets tricky when passing data from B back to A.
According to documentation, we can use SharedViewModel which is works well. But I am looking for better way of passing data back to A from B.
The problem of using SharedViewModel is, you have to create SharedViewModel for every fragment pair that you need to pass data.
Any suggestions? If any annotation-processing method you can think about, you are more than welcome to recommend.
If you do not want to use SharedViewModel way, you can follow the next approach:
1- Define a delegate for your Details Fragment. (This delegate have to implement Serializable or Parcelable:
interface DetailsFragmentDelegate: Serializable {
fun onSomething1(someData1: SomeData1)
fun onSomething2(someData2: SomeData2)
}
2- Add the delegate to your Details Fragment arguments in nav_graph.xml
3- Pass the delegate to your Details Fragment when navigating to its destination by your Base Fragment:
findNavController().navigate(
BaseFragmentDirections.actionBaseFragmentToDetailsFragment(
object: DetailsFragmentDelegate {
// override delegate methods
}
)
)
4- Get the delegate argument in Details Fragment and pass the data back wherever you need:
....
delegate.onSomething1(data1)
....
delegate.onSomething2(data2)
....
I am not sure whether there is a better way or not, but it's working...
You don't need to create a ViewModel per Fragment pair. What I am doing is creating a ViewModel per Fragment. Each ViewModel would have a map[Class[Fragment], Any] named mailBox.
Each Fragment will define a FragmentResult type which is different per Fragment class.
In the child Fragment onBackPressedHandler, before pop-up, fetch the parent ViewModel from the Activity and put your result in the mailBox for your class. You will need a ViewModel class for that. See below.
The parent Fragment needs to pass it's ViewModel.class to the child Fragment, before launching it.
When the Parent Fragment is re-started after popping up the child from the stack. Get the mailBox map from it's ViewModel, check if there is a key with value from the expected FragmentChild::class. If so, then cast to the desired type.
The parent Fragment ViewModel needs to save who was the last child it launched.
I am using an callback interface for this. So i have created an interface with some methods. I implemented that interface 'A' and then call if from 'B'. Very easy and works great.
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 have a ViewModel named SharedViewModel:
public class SharedViewModel<T> extends ViewModel {
private final MutableLiveData<T> selected = new MutableLiveData<>();
public void select(T item) {
selected.setValue(item);
}
public LiveData<T> getSelected() {
return selected;
}
}
I've implemented it based on SharedViewModel example on the Google's Arch ViewModel reference page:
https://developer.android.com/topic/libraries/architecture/viewmodel.html#sharing_data_between_fragments
It is very common that two or more fragments in an activity need to communicate with each other. This is never trivial as both
fragments need to define some interface description and the owner
activity must bind the two together. Moreover, both fragments must
handle the case where the other fragment is not yet created or not
visible.
I have two fragments, called ListFragment and DetailFragment.
Until now I used these two fragments inside an activity called MasterActivity, and everything worked well.
I got the ViewModel in ListFragment, selected the value to use it on DetailFragment.
mStepSelectorViewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
However, now, in certain cases, I need that ListFragment (a layout to a different device configuration) will be added to a different activity, called DetailActivity. Is there a way to do that similarly to the above example?
A little late but you can accomplish this using a shared ViewModelStore. Fragments and activities implement the ViewModelStoreOwner interface. In those cases fragments have a store per instance and activities save it in a static member (I guess so it can survive configuration changes).
Getting back to the shared ViewModelStore, let say for example that you want it to be your Application instance. You need your application to implement ViewModelStoreOwner.
class MyApp: Application(), ViewModelStoreOwner {
private val appViewModelStore: ViewModelStore by lazy {
ViewModelStore()
}
override fun getViewModelStore(): ViewModelStore {
return appViewModelStore
}
}
Then in the cases when you know that you need to share ViewModels between activity boundaries you do something like this.
val viewModel = ViewModelProvider(myApp, viewModelFactory).get(CustomViewModel::class.java)
So now it will use the Store defined in your app. That way you can share ViewModels.
Very important. Because in this example the ViewModels live in your application instance they won't be destroyed when the fragment/activity that uses them gets destroyed. So you will have to link them to the lifecycle of the last fragment/activity that will use them, or manually destroy them.
Well, I created a library for this purpose named Vita, You can share ViewModels between activities and even fragments with different host activity:
val myViewModel = vita.with(VitaOwner.Multiple(this)).getViewModel<MyViewModel>()
The created ViewModel in this way stay alive until its last LifeCycleOwner is destroyed.
Also you can create ViewModels with application scope:
val myViewModel = vita.with(VitaOwner.None).getViewModel<MyViewModel>()
And this type of ViewModel will be cleared when user closes app
Give it a try and kindly let me know your feedback:
https://github.com/FarshadTahmasbi/Vita
you can use factory to make viewmodel and this factor will return single object of view model.. As:
class ViewModelFactory() : ViewModelProvider.Factory {
override fun create(modelClass: Class): T {
if (modelClass.isAssignableFrom(UserProfileViewModel::class.java)) {
val key = "UserProfileViewModel"
if(hashMapViewModel.containsKey(key)){
return getViewModel(key) as T
} else {
addViewModel(key, UserProfileViewModel())
return getViewModel(key) as T
}
}
throw IllegalArgumentException("Unknown ViewModel class")
}
companion object {
val hashMapViewModel = HashMap<String, ViewModel>()
fun addViewModel(key: String, viewModel: ViewModel){
hashMapViewModel.put(key, viewModel)
}
fun getViewModel(key: String): ViewModel? {
return hashMapViewModel[key]
}
}
}
In Activity:
viewModelFactory = Injection.provideViewModelFactory(this)
// Initialize Product View Model
userViewModel = ViewModelProviders.of(this, viewModelFactory).get(
UserProfileViewModel::class.java)`
This will provide only single object of UserProfileViewModel which you can share between Activities.
I think we still get confused with the MVVM framework on Android.
For another activity, do not get confused because it must necessarily be the same, why?
This makes sense if it has the same logic (even if the logic could still be abstract in other useful classes), or if the view in the XML is almost identical.
Let's take a quick example:
I create a ViewModel called vmA, and an activity called A and I need the user's data, I will go to insert the repository in vmA of the User.
Now, I need another activity that needs to read user data,
I create another ViewModel called vmB and in it I will call the user repository.
As described, the repository is always the same.
Another way already suggested is to create N instances of the same ViewModel with the implementation of the Factory.
If you want a ViewModel that is shared by all your activities (as opposed to some),
then why not store what you want stored in that ViewModel
inside your Application class?
The trend presented at the last Google I/O seems to be to abandon the concept of Activities in favor of single-activity apps that have a lot of Fragments.
ViewModels are the way to remove the great number of interfaces the activity of an interface formerly had to implement.
Thus this aproach no longer makes for giant and unmaintainable activities.
Here's a link
Hope it helps you. O(∩_∩)O~
In addition:
1) The inspiration for the code came from smart pointer in c++.
2) It will be auto cleared when no activities or fragments references ShareViewModel.
The ShareViewModel # onShareCleared() function will be called at the same time!
You don't need to destroy them manually!
3) If you use dagger2 to inject the ViewModelFactory for share the viewmodel
between two activities (maybe three), Here's sample