How can i navigate through frtagments and have fragments state and all views state the way they were when i navigate back using Databinding? - android

I'm using databinding to make handling views and binding data to them easy for myself and i also use navigation component. Evething works fine but i can not save fragments'(destination) UI state. For example if i have a recyclerview and i scroll to last position and i navigate to another fragment and when i get back to previous fragment, i can see first recyclerview's position. Now, it's either databinding scrolled recyclerview to first position or recyclerview is recreated. First i thought it's because of navigation component that does not save destinatins views state so i read with more attention and i reached navigate to a destination which in popUpToSaveState and restoreSaveState section says:
When you use app:popUpTo to navigate to a destination, Navigation 2.4.0-alpha01 and higher allow you to optionally save the states of all destinations popped off of the back stack. To enable this option, include app:popUpToSaveState="true" in the associated element:
so in all my actions i added app:popUpToSaveState="true" but did not solve my issue. So i kept reading and i faced this title about support multiple back stack. In section of restoring state it says:
Navigation XML
In Navigation XML, elements in your navigation graph can use the app:popUpToSaveState attribute to save the state of any destinations that the action popped using app:popUpTo. They can also use the app:restoreState attribute to restore any previously saved state for the destination defined in the app:destination attribute.
so i also add app:restoreState="true" to all my actionss but did not saved destinations(fragments) views state. so i realized it's not the navigation component that causes this problem it might be databinding that does not save fragments state. So i read more about databinding. Nothing attract my attention except this title Bind layout views to Architecture Components and i saw this sample code:
class ViewModelActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
// Inflate view and obtain an instance of the binding class.
UserBinding binding = DataBindingUtil.setContentView(this, R.layout.user);
// Specify the current activity as the lifecycle owner.
binding.setLifecycleOwner(this);
}
}
so i thought i did not Specify fragments as the lifecycle owners and i added binding.setLifecycleOwner(LIFE_CYCLER_OWNER); to all fragment that i used databinding.
Here is how use databinding in fragment :
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
fragBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_season_lists, container, false);
fragBinding.setLifecycleOwner(this);
return fragBinding.getRoot();
}
and here is how i use databinding in recyclerview:
private final LayoutInflater inflater;
public SectionDataRecyclerView(
Context context,
ArrayList<SingleModel> sectionList) {
this.inflater = LayoutInflater.from(context);
this.sectionList = sectionList;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull final ViewGroup parent, int viewType) {
final ActivitySingleCourseListBinding binding = DataBindingUtil.inflate(inflater, R.layout.activity_single_course_list, parent, false, null);
binding.setLifecycleOwner(binding.getLifecycleOwner());
return new SectionDataRecyclerView.ViewHolder(binding);
}
I used both this; And getViewLifecycleOwner() for binding.setLifecycleOwner(LIFE_CYCLE_OWNER); in fragments but non of them saved views state like recyclerview position. To make sure it is the databinding that does not save states i created and test fragment with a recyclerview it and RecyclerView.Adapter in the exsisting navigation component without using databinding. it turned out that it's because of databinding that fragments are (let's say) recreating views so they can not have their state saved. It got too long so i ask my question in another way:
Question :
In case if using both navigation component and databinding how can i navigate through frtagments and have fragments state and all views state the way they were when i navigate back?

Related

Viewmodel attach to fragment is not working as expected

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

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;
}
}

Fragment vs. Custom View in Android

The Fragment and Custom View can achieve the similar function, I know that fragment is more re-usable comparing with custom view, any other benefits/enhancements for using Fragment? Is fragment supposed to replace Custom View, or just a enhancement for some specific purpose?
For instance, the code below is fragment:
public class TestFragment extends Fragment {
private TextView tv_name;
private Button btn_play;
private Button btn_delete;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.testfragment, container, false);
}
#Override
public void onStart() {
super.onStart();
tv_name = (TextView)getView().findViewById(R.id.tv_name);
btn_play = (Button)getView().findViewById(R.id.btn_play);
btn_delete = (Button)getView().findViewById(R.id.btn_delete);
}
}
The code for custom view:
public class TestCustomView extends LinearLayout {
private TextView tv_name;
private Button btn_play;
private Button btn_delete;
public TestCustomView(Context context, AttributeSet attrs){
super(context, attrs);
setOrientation(LinearLayout.HORIZONTAL);
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
tv_name = new TextView(context);
addView(tv_name);
btn_play = new Button(context);
addView(btn_play);
btn_delete = new Button(context);
addView(btn_delete);
}
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.testfragment, container, false);
}
}
Both TestFragment and TestCustomView can create a view consisting of TextView and Buttons, and use tags of Framelayout/fragment and com.packagename.TestCustomView to declare in the activity's xml layout file, but what's the advantages to use Fragment?
Fragment can be used in different scenarios but most used are:
wrapper around a view
headless fragment - i.e. no view => not very helpful in general but can be used
retainable fragment - can be any of above. By using Fragment.setRetainInstance(true) you can bypass Fragment.onDestroy(), i.e. can keep fragment data on configuration changes but fragment view structure is still destroyed/recreated
can be added to activity back stack, i.e. easy Back button previous state restore
There are cases where fragment are complete pain in the neck, then there are cases where they can achieve results quicker.
For some custom and more flexible situations fragments can get cluttered and managing them would be difficult. So dealing with views directly can be really handy and more helpful for some cases. But everything is based on requirements.
Note View has its own life cycle too and can store/recreate saved instance state. A little bit more work but it has the option too.
Custom Views have the advantage of simplicity and their primary purpose is to display a piece of data on the screen. They must rely on other components in order to do more.
Think of Fragments as a functional unit, a way to display a portion of UI that has a specific purpose, using one or more Views. Fragments are connected to the Activity lifecycle and they can include and control Loaders to populate the Views with data. They can also include sub-fragments. Finally, they can also be added to a synthetic back stack. They can do many things and are somewhat complex to learn.
As you can see, Fragments have much more in common with Activities than they have with custom views.
As a side note, Fragments can also be headless (with no UI). Headless fragments provide a way to encapsulate non-visual functionality relying on the Activity lifecycle in a separate component.
Fragments come with their own lifecycle, which can be a hinderance or a bonus, depending on what you need.
Fragments get lifecycle methods like onResume or onSavedInstanceState, which can help you deal with state transitions in your application. If you're using custom views, you need to handle that kind of things on your own.
There are people who advocate against using fragments, I suggest reading https://developer.squareup.com/blog/advocating-against-android-fragments/
The most useful functionality of using Fragments over Custom Views is that they have their own Lifecycle Callbacks, i.e. we can register our own FragmentLifecycleCallbacks to do some operations before/after Fragment creation/destruction.
We can create our own FragmentLifecycleCallbacks and register it with Activity to inject dependencies in Fragment through Dagger.
There are some workarounds to inject dependencies in Custom Views too, but doing it through FragmentLifecycleCallbacks is much cleaner and easier to do.

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.

View reuse in fragments android

I am trying to save my View states in my fragment but I am concerned I make be leaking my Activity. Here is what I am doing:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state){
if(mView != null){
View oldParent = mView.getParent();
if(oldParent != container){
((ViewGroup)oldParent).removeView(mView);
}
return mView;
}
else{
mView = inflater.inflate(R.id.fragview, null)
return mView;
}
}
I am concerned because I know all Views hold onto a context and I don't know if it is the Activity context or Application context if inflated from the inflater. Perhaps it would be a better idea to pragmatically create the view and set its attributes using getActivity().getApplication() rather than use the inflater. I would appreciate any feedback on this.
Thanks!
EDIT: Confirmed Activity leak, although this code works great don't do it :*(
I am trying to save my View states in my fragment but I am concerned I make be leaking my Activity.
Use onSaveInstanceState() (in the fragment) and onRetainConfigurationInstance() (in the activity) for maintaining "View states" across configuration changes. I am not quite certain what other "View states" you might be referring to.
I am concerned because I know all Views hold onto a context and I don't know if it is the Activity context or Application context if inflated from the inflater.
Since using the Application for view inflation does not seem to work well, you should be inflating from the Activity. And, hence, the views will hold a reference to the Activity that inflated them.
#schwiz é have implemented something similar.
I use the setRetainInstance in the fragment. on the fragment layout I have a FrameLayout placeholder where I put my webView inside.
This webView is created programmatically on the Fragment's onCreate using the Application Context, and I do a addView(webView) on the inflated layout in onCreateView().
webView = new WebView(getActivity().getApplicationContext());
And on onDestroyView I simply remove the webView from my framelayout placeholder.
It work really well, unless you try to play a video in fullscreen. That doesn't work because it expects an Activity Context
I am trying to save my View states in my fragment
In order to save and retain view's state you can just use View.onSaveInstanceState () and View.onRestoreInstanceState (Parcelable state)
It will help you to handle saving and restoring view state independantly from neither activity or fragment.
See this answer for more information about it (how to prevent custom views from losing state across screen orientation changes)

Categories

Resources