View Pagger loads two child data at same time - android

I Use a Activity that holds a Fragment Inside That Fragment I have an other Fragment that holds ViewPagger2 now when apps open its should display first child of ViewPagger2 and also lode only that data but its not happening its loads two childs that make my app slow how to solve this issue.

It's the default behaviour of the viewPager2 to load adjacent pages for smoother animation. But you can override the default offscreen page limit by using viewpager2 setOffscreenPageLimit method.
In kotlin use
viewPager2.offscreenPageLimit = PAGE_COUNT
This will load that specified number of pages in advance. But, this PAGE_COUNT cannot be less than 1 which means, It'll still load your second fragment. But, It shouldn't slow down your app as you've mentioned.
If you are doing any network request then you can set registerOnPageChangeCallback for viewPager2 and override onPageSelected, then do the network request only when that fragment is selected.
As onPageSelected(position: Int) only gives us the position of the selected page, but not the fragment itself, so we have to retrieve the selected fragment using childFragmentManager and then trigger our network load request.
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
if (childFragmentManager.fragments.size > position) {
if(position == 1) { // for second fragment
val fragment = childFragmentManager.fragments[position] as SecondFragmentClassName
fragment.loadNetworkData()
}
}
}
})
For multiple fragments you can use when statement accordingly.

Put your data loading in the Fragment's onResume method as Viewpager2 only resumes the Fragment when it is displayed.
This maintains the Fragment's encapsulation but achieves the same goal as doing it in a OnPageChangeCallback.

Related

MVVM: Communicate child Fragment to parent Fragment

I have a parent Fragment that has ViewPager which holds another Fragments(child). Some child Fragment can have list. If the user press a back button the list will scroll up if can scroll vertically, another press on back button will move the ViewPager to first item (Fragment).
While I can create an approach like this on child Fragment.
if (adapter.currentList.isNotEmpty() && recyclerView.canScrollVertically(-1))
recyclerView.smoothScrollToPosition(0)
else {
try {
val parentFragment: HomeFragment = parentFragment as HomeFragment
parentFragment.onBackPressed()
} catch (e: Exception) {
FirebaseCrashlytics.getInstance().recordException(e)
}
}
And a public method on parent Fragment like this
fun onBackPressed() {
if (viewPager.currentItem != 0)
viewPager.setCurrentItem(0, true)
else
requireActivity().onBackPressed()
}
I am not sure if this is the best thing to do since I read that Fragments should better not communicate directly with each other, instead communication should be handle by the host Activity or shared ViewModel. But doing so seems an overkill and I do not feel the use of LiveData just for this case.
I would go with the SharedViewModel approach, its kinda built for this. Your child Fragments are effecting the ViewPager that belongs to the ParentFragment. They are effectively part of the same View but the child Fragments don't have access to the View that contains them. So using a SharedViewModel can bridge the gap.
What I would do is make a well defined interface that manipulates the ViewPager and its Adapter as you see fit for your application. That interface should then be accessible through the SharedViewModel.
In the interface you could have a function called navigateToFragment(Class fragmentClass); In this function you would then need to find a fragment via its class inside the Adapter, and then use the ViewPager to go to it. Ie navigateToFragment(HomeFragment.class);

RecyclerView with StaggeredGridLayoutManager in ViewPager, arranges items automatically when going back to fragment

I am using Navigation component in my App, using google Advanced Sample(here).
my problem is when going back to a fragment, the scrolling position does not lost but it rearranges items and moves highest visible items so that top of those item align to top of recyclerview. please see this:
before going to next fragment:
and after back to fragment:
this problem is matter because some times clicked item goes down and not seen until scroll down.
how to prevent this behavior?
please consider:
this problem exist if using navigation component to change fragment. if start fragment using supportFragmentManager.beginTransaction() or start another activity and then go to this fragment it is OK. but if I navigate to another fragment using navigation component this problem is exist.(maybe because of recreating fragment)
also this problem exist if using fragment in ViewPager. i.e recyclerView is in a fragment that handle with ViewPagerAdapter and viewPager is in HomeFragment that opened with Navigation component. if recyclerView is in HomeFragment there is no problem.
no problem with LinearLayoutManager. only with StaggeredGridLayoutManager.
there is not difference if using ViewPager2 and also FragmentStatePagerAdapter
I try to prevent recreate of fragment(by this solution) but not solved.
UPDATE:
you can clone project with this problem from here
When using Navigation Component + ViewPager + StaggeredGridLayoutManager, wrong recyclerView.computeVerticalScrollOffset() has been returned during Fragment recreate.
In general, all layout managers bundled in the support library already know how to save and restore scroll position, but in this case, we had to take responsibility for this.
class TestFragment : Fragment(R.layout.fragment_test) {
private val testListAdapter: TestListAdapter by lazy {
TestListAdapter()
}
private var layoutManagerState: Parcelable? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
postListView.apply {
layoutManager = StaggeredGridLayoutManager(
2, StaggeredGridLayoutManager.VERTICAL
).apply {
gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
}
setHasFixedSize(true)
adapter = testListAdapter
}
testListAdapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT
}
override fun onPause() {
saveLayoutManagerState()
super.onPause()
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
restoreLayoutManagerState()
}
private fun restoreLayoutManagerState () {
layoutManagerState?.let { postListView.layoutManager?.onRestoreInstanceState(it) }
}
private fun saveLayoutManagerState () {
layoutManagerState = postListView.layoutManager?.onSaveInstanceState()
}
}
Source code: https://github.com/dautovicharis/MyStaggeredListSample/tree/q_65539771
The Navigation Component behavior is normal when you navigate from one fragment to another. I mean, onDestroyView() method from the previous fragment is executed, so it means that your view is destroyed, but not the fragment. Remembering that fragment has two lifecycles one for the fragment and another one for the view, There was a video about it.
Also, there were issues registered in issue tracker in order to avoid this behavior in some cases and the GitHub issues:
https://issuetracker.google.com/issues/127932815
https://github.com/android/architecture-components-samples/issues/530
The problem is that when you have fragment that is heavy to recreate, is easier to do not destroy it and just add one fragment. So, when you go back it is not recreated. But, for this behavior is not part of navigation component.
Solutions
The easiest solution is to not use navigation component and work with the tradicional way, as you can see this works perfectly in you use case.
You can use the traditional way just for this use case, and use the navigation component for other cases.
You can inflate this view in an activity. So you are adding un activity
But if the previous tree options is not possible. You can try the following:
If you are using viewModel, you can use SaveState. Basically, it can save the data from your fragment, it is like a map data structure, so you can save positions from your list or recycler view. When go back to this fragment, get the position from this saveState object and use scrollToPosition method in order to add the real position.
Recycler view have methods for restore positions. You can see the uses cases for that, because first you need the data and then add the real position, for more details you can visit this link. This configuration for recycler view is useful also when you lose memory and you need to recreate the recycler view with asynchronous data.
Finally, if you want to understand more about how fragment works with navigation component, you can see this link

VIewPager in recycler view does not load data onResume

I have added a ViewPager in RecyclerView. The issue is that, if i press Home Button, or set activity to pause state, and resume activity again, it does not load fragments inside the view pager.
ViewPager tabs are there, but fragments loads blank.
When i debugged, i find out that, onCreateView does not get called in viewPager's fragment, when onResume() is called for activity,
Following is my code in RecyclerAdapter.
private fun bindTabViewHolder(viewHolder: HomeRecyclerAdapter.TabViewHolder, position: Int) {
viewHolder.tabLayout.setupWithViewPager(viewHolder.viewPager)
//Creating View Pager Adapter Instance
viewPagerAdapter = BaseViewPagerAdapter(fragmentManager)
viewPagerAdapter?.addFragment(homeFragment, dataList?.get(position)?.teamName) // this line can crash
viewPagerAdapter?.addFragment(awayFragment, dataList?.get(1)?.teamName) // this line can crash
viewHolder.viewPager.adapter = viewPagerAdapter
}
I think if it starts calling onCreateView() for fragments, issue will be resolved, Also if i try to get fresh data from the feed, by pull to refresh, if does not load fragments inside viewPager.
You should generally never put a ViewPager that hosts Fragments inside a RecyclerView, as the IDs / tags in the FragmentManager will get confused.
You will need to use a ViewPager in the RecyclerView that hosts Views (by subclassing PagerAdapter, instead of FragmentPagerAdapter) instead.

Android ViewPager2 how to retrieve the fragment onBindViewHolder

ViewPager release 1.0.0 version
For a normal RecyclerView, holder.itemView gives me the view currently binding/ rendering.
However, FragmentStateAdapter's holder.itemView only gives me a FrameLayout
My ViewPager adapater:
class MyFragmentStateAdapter(activity: FragmentActivity) :
FragmentStateAdapter(activity) {
var items = mutableListOf<String>()
override fun createFragment(position: Int): MyPageFragment {
val itemText = items[position]
return MyPageFragment.create(itemText)
}
override fun getItemCount(): Int = items.size
override fun onBindViewHolder(
holder: FragmentViewHolder,
position: Int,
payloads: MutableList<Any>
) {
super.onBindViewHolder(holder, position, payloads)
val fragment = ??? as MyRefreshableInterface
fragment.refresh()
}
fun update(mutableListOf: MutableList<String>) {
this.items = mutableListOf
notifyDataSetChanged()
}
}
Context
I have a small and fix number of tabs displaying different information section of a user profile. Upon certain events, I need to refresh AUTOMATICALLY all the tabs ASAP. In other words, I need to refresh the currently-hidden tabs besides the current tab user is looking at.
ASAP = the next time user visits a currently-hidden tab. User goes there, first thing they see is a loading animation. That's good
Why not immediately?
Because hidden fragments could be detached/ destroyed. User is not looking at the right now anyway. Keeping all the fragments in the memory is also expensive
When user navigates to previous hidden tab say Addresses, onBindViewHolder will be triggered (This is great compared to ViewPager 1) However, the gap is that I have no reference of the currently-selected fragment
Other findings
I've already try referencing fragmentActivity.supportFragmentManager.fragments but it seems to have maximum of 4 fragments whereas I have 6 fragments/ pages, for the sake of testing
You seem to be asking 2 different things.
I need to refresh automatically all the tabs. In other words, I need to refresh the currently-hidden tabs besides the current tab user is looking at.
and
When user navigates to previous hidden tab say Addresses, onBindViewHolder will be triggered.
You are trying to do the update when displayed on this second item.
This "Update when displayed" is easy with viewpager2, when a Fragment is displayed the Fragment is brought up to lifecycle state "Resumed" from "Started".
Thus create an update method in each Fragment and then in the Fragments onResume method call the update method.
(This is what I do in my App)
Update:
The docs on this behaviour is bad in viewpager2, the best I can find is the comment in the source code https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/viewpager2/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java#634 of
/**
* Pauses (STARTED) all Fragments that are attached and not a primary item.
* Keeps primary item Fragment RESUMED.
*/
Or the release notes https://developer.android.com/jetpack/androidx/releases/viewpager2#1.0.0-alpha06
FragmentStateAdapter: non-primary-item Fragments are capped at STARTED, and their menuVisibility is set to false.
Viewpager 1 in Androidx has been updated with BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT to match the behaviour of Viewpager2
See https://developer.android.com/reference/androidx/fragment/app/FragmentStatePagerAdapter#BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
The "Update all Fragments" is bit harder but I believe you can do the following (sorry my Kotlin is not up to scratch to give code example.
In the Adapter Constructor create and store a List of the Fragments (Not just itemText)
e.g. foreach item in items add to list of Fragments MyPageFragment.create(itemText)
Then in createFragment just return the Fragment from the correct position of the list.
e.g. return fragments[position]
Then in your adapter update you iterate over all items in your List of Fragements calling update on each Fragment.
(This is basically maintaining your own list of Fragments in the adapter instead of creating them on the fly when createFragment is called)
Update:
As noted in the comments this is less than ideal and breaks the Fragment lifecycle concepts and efficiency of Viewpager2, the update in onResume of Fragment is better.

Load Fragment only when Tab clicked

I want my fragment to load only when the tab is clicked. That is I am calling a webservice on each fragment, so I want that webservice to be called only when user clicks the specific tab; loads the fragment.
My Fragments are attached to the view pager.
I have override the following method in my fragments: setUserVisibleHint
override fun setUserVisibleHint(isFragmentVisible: Boolean) {
super.setUserVisibleHint(true)
if (this.isVisible) {
// we check that the fragment is becoming visible
if (isFragmentVisible && !isLoadOnce) {
callAPI(param)
isLoadOnce = true
}
}
}
the variable is set as: private var isLoadOnce = false in the fragment class.
I have 3 fragments in number the problem is when my activity popsup, the first fragment is visible and if I click the last tab that is the third tab to load the third fragment, nothing happens that is the web service won't call at all.
But when I click the second fragment and then the third fragment, and yes the webservice then only calls
So I want to call the web service whenver the user clicks each fragment (number 2 fragment or number 3 fragment)!
Can somebody please figure out what I am doing wrong?
I think there are at least 2 solutions to your problem:
Call "callApi(param)" on onResume() of the fragment;
Override onPageSelected(int) and call "callApi(param) in it.
Let me know if this worked for you!
viewpager generally loads the side views to provide smooth swipe experience.
you can restrict the viewpager to load the view using
viewpager.setOffScreenLimit(0)

Categories

Resources