I have main fragment with ViewPager2 that contains other fragments.
Each child fragment can open another detail fragment (main fragment replaced with details fragment).
When I go back to from detail fragment, ViewPager2 recreate all child fragments.
If I try to reuse the adapter (FragmentStateAdapter):
if (pagerAdapter == null) {
pagerAdapter = Adapter(this)
binding.viewPager.adapter = pagerAdapter
}else{
binding.viewPager.adapter = pagerAdapter
}
I got crash:
java.lang.IllegalArgumentException
at androidx.core.util.Preconditions.checkArgument(Preconditions.java:38)
at androidx.viewpager2.adapter.FragmentStateAdapter.onAttachedToRecyclerView(FragmentStateAdapter.java:132)
at androidx.recyclerview.widget.RecyclerView.setAdapterInternal(RecyclerView.java:1209)
at androidx.recyclerview.widget.RecyclerView.setAdapter(RecyclerView.java:1161)
at androidx.viewpager2.widget.ViewPager2.setAdapter(ViewPager2.java:461)
How to avoid creating new fragment every time?
I solved the problem. I init adapter every time in onCreate, before I used lazy for init adapter.
logVP2.adapter = SummaryDetailsPageAdapter(childFragmentManager, lifecycle).apply {
setData(args?.list)
}
Related
The Activity contains a ViewPager with multiple tabs populated by Fragments. When the system unloads the application from memory due to lack of resources, when the application is restored, the same Activity is restored, instances of the fragments that existed at the time of destruction are attached to it. At the same time, Activity.onCreate contains the standard logic for creating and filling the ViewPager with fragments:
ViewPagerAdapter pagerAdapter = new ViewPagerAdapter(getSupportFragmentManager(), ProjectActivity.this);
for (ProjectData projectData : projectData) {
pagerAdapter.addFragment(ProjectFragment.newInstance(projectData));
}
viewPager.setAdapter(pagerAdapter);
As a result, the Activity contains references to the newly created fragment instances, and the old (restored) fragment instances are attached to it.
At the moment, a decision has been made - at the start of the Activity, check whether the stack of fragments is empty, if not, delete existing fragments, and standardly fill the adapter with new instances of fragments:
FragmentManager fragmentManager = getSupportFragmentManager();
List<Fragment> oldFragments = fragmentManager.getFragments();
if (!oldFragments.isEmpty()) {
FragmentTransaction transaction = fragmentManager.beginTransaction();
for (Fragment fragment: oldFragments) {
transaction.remove(fragment);
}
transaction.commit();
fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
for (ProjectData projectData : projectData) {
pagerAdapter.addFragment(ProjectFragment.newInstance(projectData));
}
viewPager.setAdapter(pagerAdapter);
I suspect that this is not the right solution, but how to solve this issue in a standard way?
I also do not understand why when calling viewPager.setAdapter(pagerAdapter) new instances of fragments loaded into the adapter do not go through their life cycle until you clear the fragment stack.
Well I have a tablayout, inside the tablayout is 3 tabs(fragments) and inside of the fragment i need to update the whole tablayout using swiperefreshlayout. anyone knows how to do that?
You can get parent fragment which hold child fragments in a child fragment like this:
private FragmentMain getMainFragment() {
return (FragmentMain) getParentFragment(); }
Then you can call a method from a child fragment to all other child fragments for overcome whatever task you want to accomplish.
if((MainFragmentPagerAdapter) getMainFragment().viewPager.getAdapter().getItem(1) instanceof ChildFragment1){
((ChildFragment1) ((MainFragmentPagerAdapter)getMainFragment().viewPager.getAdapter()).getItem(1)).setTask();
} else if((MainFragmentPagerAdapter) getMainFragment().viewPager.getAdapter().getItem(2) instanceof ChildFragment2){
((ChildFragment2) ((MainFragmentPagerAdapter)getMainFragment().viewPager.getAdapter()).getItem(2)).setTask();
}
Below is my code. What i am trying to achieve is that i am displaying viewholder inside my Recycler view. Inside view pager i am displaying one fragment and on swipe left i am displaying another fragment. But when i run the app. App is crashing.Don't know where i am going wrong. I think it's some where in fragment's concept. Please do help me
#Override
public void onBindViewHolder(ManageCustomerViewHolder holder, int position)
{
holder.viewPager.setAdapter(new MyPageAdapter(fragmentManager, fragments));
}
private List<Fragment> getFragments()
{
List<Fragment> fList = new ArrayList<Fragment>();
fList.add(MyFragment.newInstance("Fragment 1"));
fList.add(Myfragment1.newInstance("Fragment 2"));
return fList;
}
public class ManageCustomerViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener
{
ViewPager viewPager;
viewPager = (ViewPager) itemView.findViewById(R.id.viewpager);
itemView.setOnClickListener(this);
}
This is what the error is :
java.lang.IllegalStateException: Fragment has not been attached yet.
I was facing the same issue in my app.
Stack Trace:
java.lang.IllegalStateException: Fragment has not been attached yet. android.support.v4.app.Fragment.instantiateChildFragmentManager(Fragment.java:2154)
android.support.v4.app.Fragment.getChildFragmentManager(Fragment.java:704)
The app was crashing at this line:
function performSomeActionInChildFragments() {
List<Fragment> fragments = getChildFragmentManager().getFragments();
...
}
This happens when the fragment is no longer attached to its parent activity/fragment. So a simple isAdded() check should resolve this issue for you.
Fix:
function performSomeActionInChildFragments() {
if (!isAdded()) return;
List<Fragment> fragments = getChildFragmentManager().getFragments();
...
}
From documentation:
isAdded() - Return true if the fragment is currently added to its activity.
Update the Support Library Revision to 27.0.2 to solve the problem. See changelog.
you can do this
FragmentManager childFragmentManager ;
in onCreate :
childFragmentManager = getChildFragmentManager();
then you can use childFragmentManager in
ViewPagerAdapter adapter = new ViewPagerAdapter(childFragmentManager);
adapter.addFragment(tabLlegadaFragment, "Llegada");
adapter.addFragment(tabIngresoFragment, "Ingreso");
viewPager.setAdapter(adapter);
`
and don't forget before do that you have to check your activity was added or not.
you can try this way:
if(getActivity()!=null && isAdded()) //do now
I faced this exception today. After looking for answer I noticed that all are related with ViewPager and getFragmentManger() or in my case getChildFragmentManager().
Well sometimes this things happen when there is no fragment so nothing can be attached or shown. Therefore when my fragment was created I now save in a variable the fragment manager, then I use that manager in my Viewpager instance so there will allways be a manager saved in the class.
To clarify what I'm saying here's my code:
Before
ViewPagerAdapter adapter = new ViewPagerAdapter(getChildFragmentManager());
adapter.addFragment(tabLlegadaFragment, "Llegada");
adapter.addFragment(tabIngresoFragment, "Ingreso");
viewPager.setAdapter(adapter);
Here I faced the error because I recreated the view with some method and I also pressed the back button. So when that happened Android is looking for a getChildFragmentManager() from a Fragment that no longer exists or is gone (because I pressed the back button)
Now
FragmentManager childFragMang;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_tab_main, container, false);
..
childFragMang= getChildFragmentManager();
..
return view;
}
and when I call Viewpager I use that manager
ViewPagerAdapter adapter = new ViewPagerAdapter(childFragMang);
adapter.addFragment(tabLlegadaFragment, "Llegada");
adapter.addFragment(tabIngresoFragment, "Ingreso");
viewPager.setAdapter(adapter);
I did not face that exception again so it was because my fragment had dissapeared when I pressed back and there were no getChildFragmentManager() to call
In all cases,when you using getChildFragmentManager, add isAdded() will beneficial to our application.
You shouldn't be trying to instantiate Fragments. Fragments are created and have lifecycles that are controlled by the Android system. You respond to lifecycle events by overriding callback methods like onAttached (called when the fragment is attached to its Activity)
My ViewPager is located in nested fragment, because i also have navigation drawer. My question is how can i call instance of viewpager in MainActivity.java, so i can set current item of viewpager using method setCurrentItem() from MainActivity.java in method onBackPressed(), when for example user is on third fragment of viewpager and when he presses back button he should be returned on first fragment.
First, you have to declare RecyclerView as public. Not sure if it has to be static, but it'll tell you if it has to.
Second, you need to call an instance of the fragment that holds the RecyclerView.
For example, Fragment recyclerFrag = new RecyclerFrag();
After that, try calling your RecyclerView from that instance and use setCurrentItem(). For example, recyclerFrag.recyclerView.setCurrentItem(0).
I have found a solution. You should first create one method in FragmentActivity where is initialized your ViewPager and set return type ViewPager. Something like this:
public ViewPager getViewPager() {
return mViewPager; // instance of view pager
}
After that create object of your Fragment in Activity where you are calling instance of view pager like this:
YourFragment fragment = new YourFragment();
And in my case i should call viewpager in onBackPressed():
HomeFragment fragment1 = new HomeFragment();
if (fragment1.getViewPager().getCurrentItem() == 1 || fragment1.getViewPager().getCurrentItem() == 2) {
fragment1.getViewPager().setCurrentItem(0);
} else {
super.onBackPressed();
}
I started a new project using tabbed activity of Android Studio. It created an activity with ViewPager and 3 fragments = fragment1, fragment2 and fragment3.
I changed fragment1 to have a listView and a TextView, that I initialize when the fragment is created. The data in the listView is read from an SQL database.
When the application starts, fragment1 is displayed and the listView shows the data read from SQL.
When I swipe from fragment1 to fragment2 and back, fragment1 shows the listview properly. However, when I swipe to fragment3 and back to fragment1 the listview data is lost and I get a blank screen, although the textview shows properly.
When I change the application to have only 2 fragments it does not happen and I can swipe from fragment1 to fragment2 and back many times without any loss of data. The moment I add the 3rd fragment, the data is lost on first swipe to fragment3, but is not lost if I swipe between fragment 1 and fragment2 only.
Any idea why is it happening?
This is happening because by default ViewPager has a function named setOffscreenPageLimit(int i). This tells ViewPager how many of the Fragments need to store in memory. By default it is '2'. So when you swipe to Fragment 2 from Fragment 1 it will still keep Fragment 1 in memory and won't destroy it from memory. But when you swipe to Fragment 3 it will have Fragment 2 and Fragment 3 in memory so it will remove Fragment 1.
That's why when you come back to your first Fragment it have lost its views or data.
Try to use
mViewPager.setOffscreenPageLimit(your total fragment count +1);
Or much better solution
Do not replace your fragment when you swipe in ViewPager. Push it to the backstack and when you comeback to it, Check for the backstack and if fragment is there then just Pop it and display it.
Here is the code:
public void pushFragments(String tag, Fragment fragment) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
if (manager.findFragmentByTag(tag) == null) {
ft.add(R.id.frame_container, fragment, tag);
}
String tagOne="com........Frag_one"; //your fragment class name
String tagTwo="com........Frag_two";
String tagThree="com........Frag_three";
String tagFour="com........Frag_profile";
Fragment fragmentOne = manager.findFragmentByTag(tagOne);
Fragment fragmentTwo = manager.findFragmentByTag(tagTwo);
Fragment fragmentThree = manager.findFragmentByTag(tagThree);
Fragment fragmentFour = manager.findFragmentByTag(tagFour);
manager.executePendingTransactions();
// Hide all Fragment
if (fragmentOne != null) {
ft.hide(fragmentOne);
}
if (fragmentTwo != null) {
ft.hide(fragmentTwo);
}
if (fragmentThree != null) {
ft.hide(fragmentThree);
}
if (fragmentFour != null) {
ft.hide(fragmentFour);
}
// Show current Fragment
if (tag.equals(tagOne)) {
if (fragmentOne != null) {
ft.show(fragmentOne);
}
}
if (tag.equals(tagTwo)) {
if (fragmentTwo != null) {
ft.show(fragmentTwo);
}
}
if (tag.equals(tagThree)) {
if (fragmentThree != null) {
ft.show(fragmentThree);
}
}
if (tag.equals(tagFour)) {
if (fragmentFour != null) {
ft.show(fragmentFour);
}
}
ft.commit();
}
And Here is how you call it:
Fragment fragment = new Frag_one();
pushFragments(fragment.getClass().getName(),fragment);
Issue solved. The problem is due to the adapter keeping 3 fragments in memory, as pointed by #Daniel Nugent. The data in the pages is loaded from DB so that when the view is recreated, when fragments are destroyed and recreated as the user swipes, the listview adapter has lost the data, and one has to repopulate it. The problem does not appear on simple examples because usually the data presented is static and is loaded in the onCreateView method.