Viewmodel attach to fragment is not working as expected - android

I'm kind of new in this Android Architecture, and I'm trying to understand the use of ViewModel.
Currently I have an activity with a fragment embedded in its xml. The fragment just have an edittext whose content I want to persist when the phone rotates. I've implemented the Viewmodel, livedata and observers correctly (I suppose), and attached the ViewModel to the fragment.
The problem comes here, when I rotate the phone the fragment is recreated and the information, gone. But, if I attached the ViewModel to the activity the app works as intended.
So, after some background info, the question is, Why would I want to attach the ViewModel to a fragment if the Viewmodel object is cleared when the fragment is recreated/destroyed?
Thanks
Ps. Here is how I attached the Viewmodel to the activity, this way it works as intended
Fragment
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
final MuestrasViewModelFactory mvmFactory = new MuestrasViewModelFactory(new String[]{"hello","world","again"});
final MuestrasViewModel mvm = new ViewModelProvider(requireActivity(), mvFactory).get(MuestrasViewModel.class);
... //here comes the observers

Related

Fragment lose data from ViewModel after recreating view

Imagine that you have 2 fragments connected to one (or more) viewModel(s) and inside of activity you'll switch between them. Once you open fragment, viewModel works as expected, so I start listening for changes from onCreate method, code example:
viewModel = new ViewModelProvider(requireActivity(), new InventoryTasksFactory()).get(InventoryTasksViewModel.class);
viewModel.inventoryTasksResponse().observe(this, new Observer<Response<List<InventoryTask>>>() {
#Override
public void onChanged(Response<List<InventoryTask>> listResponse) {
handleResponse(listResponse);
}
});
But when you switching to another fragment and going back, fragment becomes blank. I understand that fragment listening changes inside of viewModel, and you should manually getting value from viewModel and I get value from viewModel inside of onCreateView method, code example:
Response<List<InventoryTask>> inventory = viewModel.inventoryTasksResponse().getValue();
if (inventory!=null){
handleResponse(inventory);
}
Problem is that Response has 3 states: Running, Success, Error, and depends on those states view is updating. So, in first fragment opening, view updating twice and it leads to skipping frames and display blinking.
I was thinking about keeping data inside of fragment, but I want to avoid data duplicating. Besides of that, in case of sharedViewModel, you'll get issues about updating data inside of fragment!
Please, help me!
Observing your data from onViewCreated(View view, Bundle savedInstanceState) might work out.

Load data to a list fragment tab on the creation of the MainActivity

I am absolutely in love with these new components Android is introducing. So, I am building a standard mobile application with solely one activity using the Navigation components and Architecture components such as a View Model as I am performing a lot of communication with my data that I stored in room.
In one of my bottom navigation tabs, I have a list that is loaded from all my data in room. So far, I have set up my RecyclerView and my adapter in the OnCreateView() (only function used in this fragment) of this list fragment and every thing shows successfully.
The problem is that every time (especially more at first view) the fragment takes a solid 10 seconds to display all the data (which is normal considering there is a lot of it).
My question: Is there a way the adapter and and RecylcerView of this specific fragment could be setup (and load all my data) in the OnCreate() of my sole activity? So that when I view the fragment for the first time, everything pops up right away.
Also, how would I go about using OnPause() of the list fragment so that when I am on another tab, the list fragment doesn't get destroyed and when we go back on it, it displays right away?
Fetch all data from room inside onCreate() method of fragment. The onDestroyView() method calls everytime you moves away from the fragment.
To prevent recreation of views inside fragment store view in a variable.
Example:
class YourFragment extends Fragment{
View rootView;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
//fetch data from room
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState){
if(rootView == null)
rootView = inflater.inflate(R.layout.your_fragment_layout, container, false);
return rootView;
}
}

Support Fragment Manager .replace() and ViewModel

It appears that whenever you use the .replace() method within a transaction with the Support Fragment Manager the ViewModel is recreated. Is this intentional? The Fragment instance itself is not changing and the ViewModel will be (partially) preserved during rotation/configuration changes.
I'm seeing the following scenarios:
Get View model ref (count = 0), update count = 1, rotate, count = 1, onCreate called again and count = 0 (view model recreated).
Call .replace() and view model is recreated (activity and fragment instances unchanged).
Using support library 26.0.0.
The ViewModel is being created in onCreate of my fragment and is scoped to the Fragment:
viewModel = ViewModelProviders.of(this).get(DashboardViewModel::class.java)
Anyone shed some light on if this is normal?
As #CommonsWare mentioned, the viewmodel instance inside fragment should be the same in Activity.
Therefore, inside the Activity , you should do something like this
MyViewModel vm = ViewModelProviders.of(this).get(MyViewModel.class);
Inside the Fragment , you should do something like this
MyViewModel vm = ViewModelProviders.of(getActivity()).get(MyViewModel.class);
In result, they will use the same instance.
However, if you try to use it inside the fragment
MyViewModel vm = ViewModelProviders.of(this).get(MyViewModel.class);
The Viewmodel will be recreated in the fragment when you rotate the device. Since the instance is preserved inside the fragment not the activity, when the activity recreated, the fragment will also be recreated, and the MyViewModel instance.
Try to take a look on the example on master detail fragment (which might be easy to resolve your problem) ViewModel in Android Developer
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends LifecycleFragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// update UI
});
}
}
I have also made the simple Master Detail template on the github
SimpleDetailActivity.java
SimpleDetailFragment.java
replace() is suppossed to be called when you make different fragment. For the same fragment you call update().
replace() method means you can replace your current Fragment with something different fragment, which has something different layout (physical structure). You can't even guarantee that it starts from the same memory that the previous fragment used. ViewModel is kind of layout for whole container. So, for the object with different physical structure and (possibly with different memory -- I am writing possibly because you can replace with the same fragment also), you have to recreate different ViewModel to define it's container. This is because one ViewModel object points one reference Container, next time you have different fragment, your fragment container is defined by ViewModel is somewhere else, so you need another ViewModel object to point that Fragment Container.
But when you do update(), or rotate(), you guarantee the updated fragment's memory space can decrease/increase but still it's starting memory remains same. So no need to create the ViewModel. This is because your old ViewModel object is referencing the same old Fragment's container.
When you do create(), it creates GUI everything, so, obviously, there happen ViewModel creation again.
The tracking of ViewModel count is based on the above explanation.
The ViewModel is being created in onCreate of my fragment and is scoped to the Fragment. This is kind of power delegation to the fragment.

What does "State" mean in Android fragment

In android, we keeps talking about retain the activity state/fragment state, but I have this question, what does "state" mean indeed. For example, suppose I have the following DialogFragment
public class Dialog extends DialogFragment {
private String mMessage;
#Override
public void onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
View v = super.onCreateView(inflater, container, savedInstanceState);
((TextView) v.findViewById("message")).setText(mMessage);
}
}
But wouldn't "mMessage" be retained as a member variable during device rotation? So in this case, does "mMessage" considered a state that I have to retain and put into argument when creating this fragment?
On a device rotation, the currently visible Activity is destroyed. Some widgets such as DialogFragment save and restore their own state.
Handling Configuration
Activity Lifecycle
The concept of State comes from OOP, not from android, to simplify: an object has a state (the data) and a behavior (the code).
Fragments and Activities work a bit different, fragments will retain the state if they are stopped, but they will lose it of the activity that manages them is destroyed (unless you retain it). Activities, however, will lose the state upon change of configuration.
The doc explains the lifecycle and how/when to retain fragments:
https://developer.android.com/guide/components/fragments.html#Lifecycle

Fragment save the state of a listener

In an Android Fragment, which is a part of a ViewPager, there is a ListView with EditText for filtering.
filterEditText = (EditText) view.findViewById(R.id.filter_friends);
filterEditText. addTextChangedListener(textWatcher);
When I navigate to another Fragment ondestroyview is called and then when I navigate back to this fragment onCreateView is called and the filtering doesn't work anymore, though the instance variables still exist.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_new_game_facebook, container,
false);
return view;
}
How this situation should be handled correctly?
You are probably using a FragmentStatePagerAdapter, which will destroy all non-active fragments to save memory. This is helpful for when you do not know how many fragments you will use until runtime, but is overly aggressive if you know which fragments will be in the pager. Try using a FragmentPagerAdapter instead, which will not destroy the fragments as soon as you navigate away from them, so you will not need to manually persist the state of each fragment and reload it.

Categories

Resources