Just curious about the Performance difference between these 2 approaches:
Approach 1: Directly return new Fragment object for each position
class ProductAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
override fun getItemCount() = 5
override fun createFragment(position: Int) = ProductPageFragment() //here
}
Approach 2: Prepare Fragments in advance and return by position
class ProductAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
private val fragments = mutableListOf<ProductPageFragment>()
init {
repeat(5) { fragments.add(ProductPageFragment()) }
}
override fun getItemCount() = 5
override fun createFragment(position: Int) = fragments[position] //here
}
The performance difference will depend on the Fragment and what it does where, but there usually little difference because
When a fragment is instantiated, it begins in the INITIALIZED state. For a fragment to transition through the rest of its lifecycle, it must be added to a FragmentManager. The FragmentManager is responsible for determining what state its fragment should be in and then moving them into that state.
From https://developer.android.com/guide/fragments/lifecycle
Most fragments design do most of their work at higher lifecycle states e.g. at CREATED, therefore there will be little difference in performance as the lifecycle management by viewpager2 will be the same for each approach.
Related
I have couple of fragments (lets say A,B and C) replacing each other. In fragment A, there is a recyclerView which should be updated as an observer receives changes in a liveData. But the observer does not receive updates when I move to fragment B because fragment A is already destroyed.
I have changed the the lifeCycle owner from viewLifeCycleOwner to this (fragment) but still the same result.
Here is some snippet of my code:
class MainActivity: AppCompatActivity() {
val fragments = listOf(fragmentA(), fragmentB(), fragmentC())
private fun moveToFragment(index: Int) {
if (fragments.isNotEmpty()) {
viewModel.selectedPage = index
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, fragments[index])
transaction.commit()
}
}
}
class FragmentA(): Fragment{
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
fragmentAViewModel.listOfThings.observe(viewLifecycleOwner) { things ->
things?.let {
when (things.status) {
Status.SUCCESS -> {
thingsAdapter.submitList(things.data)
}
else -> {
thingsAdapter.submitList(emptyList())
}
}
}
}
}
I know that "replace" destroys the fragments but I also tried using HIDE/SHOW fragments but this pattern overlays fragments on top of each other and I wasn't able to get it fixed.
My question is what the best practice is here to make sure that the observer/recyclerView is already updated before going to the fragment. Otherwise the recyclerView will have old values and they change in few milliseconds after I go the fragmentA. The most obvious situation is when the recyclerView is supposed to be empty but when I go to fragmentA items are still there for a moment before they disappear.
I have a viewpager2 with FragmentStateAdapter() adapter inside it. I also have a tab layout with 4 tabs. I use a single fragment for all tab. that is named AllOrdersTab.
in my architecture I just send different value to load different API data to AllOrdersTab fragment.
when each tab layout is selected , a fragment created and works fine for the first time for all 4 fragments. after that if I swipe back to previous tab it is not created or refreshed again.
I want to recreate the fragment or a way to call API again when swiping between tabs. I also read this page.
FragmentStateAdapter not recreating currentFragment after notifyDataSetChanged
I tried to do this. so I decided to create 4 instance of Allorderstab() fragment. but never work for me because I guess hash codes of fragments are same.
ViewPager2 Adapter:
class ViewPagerOrdersAdapter(fm: FragmentManager,val listFragments:MutableList<Fragment>, viewlifecycler: Lifecycle) : FragmentStateAdapter(fm, viewlifecycler)
{
override fun getItemCount(): Int
{
return listFragments.size
}
override fun createFragment(position: Int): Fragment {
val args = Bundle()
when (position) {
1 -> {
args.putString("KEY_ID", "inProgress")
listFragments[position].arguments = args
return listFragments[position]
}
2 -> {
args.putString("KEY_ID", "cancel")
listFragments[position].arguments = args
return listFragments[position]
}
3 ->
{
args.putString("KEY_ID","deliver")
listFragments[position].arguments=args
return listFragments[position]
}
else -> {
args.putString("KEY_ID", "all")
listFragments[position].arguments = args
return listFragments[position]
}
}
}
override fun getItemId(position: Int): Long {
return listFragments[position].hashCode().toLong()
}
override fun containsItem(itemId: Long): Boolean {
return listFragments.find {it.id.hashCode().toLong() == itemId } != null
}
}
Here I created 4 instance of Allorderstab() Fragment.
Set ViewPager2 Adapter
MainFragment:
val fragments:MutableList<Fragment> = mutableListOf(Allorderstab(), Allorderstab(), Allorderstab(), Allorderstab())
vp.setAdapter(ViewPagerOrdersAdapter(this.childFragmentManager,fragments, lifecycle))
AllOrderstab Fragment:
override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?, savedInstanceState: Bundle?): View? {
val bundle = arguments
bundle?.let {
val myStatus = bundle.getString("KEY_ID")
myStatus?.let{
//getting history of each tab orders - calling API
myviewModel.getOrdersHistory(tempTn,myStatus)
}
}
}
All in all I don't know how to refresh while swiping between tabs . if I have a single fragment for all 4 tabs.
For those who wasted a day for this problem like me , wanting to call API each time for refreshing data, Move your code to onResume function. fortunately it is run each time your fragment visible.
AllOrderstab Fragment:
override fun onResume() {
val bundle = arguments
bundle?.let {
val myStatus = bundle.getString("KEY_ID")
myStatus?.let{
//getting history of all orders
myviewModel.getOrdersHistory(tempTn,myStatus)
}
}
super.onResume()
}
You have 4 different instances of the same tab. If you need to refresh the tabs everytime the user navigates to your tab you need to add a PageChangeListener to your tabs view. Then whenever you change the page you need to notify the fragment that it has come to foreground you can do so by calling a method on Allorderstab class and then refreshing the data from this method.
I have one MainActivity with -> FragmentA which contains -> ViewPager with (Fragment1,Fragment2,Fragment3)
Now in FragmentA I have one spinner and any selection must reflect the changes inside viewpager's currently visible fragment.
How can I achieve that? I don't want to follow ViewModel or EventBus approach for now as I am working on very old project. I want to use interface to communicate between them.
Create an interface inside your FragmentA
interface OnSpinnerValue{
fun onSpinnerValueChanged()
}
Create a WeakReference for the current selected fragment
private var _currentPage: WeakReference<OnSpinnerValue>? = null
private val currentPage
get() = _currentPage?.get()
fun setCurrentPage(page: OnSpinnerValue) {
_currentPage = WeakReference(page)
}
Now implement this interface in every child fragment of ViewPager
class Fragment1() : Fragment(), OnAddEvent {
override fun onSpinnerValueChanged() {
// implement your method
}
}
And, update currentPage value of the FragmentA, according to the selected fragment, and update it in onResume() of each child fragment
override fun onResume() {
super.onResume()
(parentFragment as FragmentA).setCurrentPage(this)
}
Now, trigger onSpinnerValueChanged from your spinner's onItemSelected methods
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
currentPage?.onSpinnerValueChanged()
}
It is strange if you don't want to use ViewModel ,
I know may be it is not best solutions but you can :
you can create function itIsYourFunctionToUpdate() inside your fragment and update him on FragmentA() , calling function with her object like fragmentB.itIsYourFunctionToUpdate()
or
if (ViewPager.getCurrentItem() == 0 && page != null) {
((FragmentClass1)page).itIsYourFunctionToUpdate("new item");
}
Also you can update viewPager like this mAdapter.notifyDataSetChanged() and all fragment inside viewPager must be updated
This question already has answers here:
Get current fragment with ViewPager2
(18 answers)
Closed 1 year ago.
i used this ViewPager2 dependency:
implementation 'androidx.viewpager2:viewpager2:1.0.0'
then i wrote code and make Adapter for FragmentStateAdapter like below:
private inner class ViewPagerAdapter(fragmentManager: FragmentManager,
lifecycle: Lifecycle, config: TribunConfig?)
: FragmentStateAdapter(fragmentManager, lifecycle) {
private val config: TribunConfig?
override fun createFragment(position: Int): Fragment {
return TribunNewsFragment.newInstance(if (menuTribun!!.get(position) == "News") "home"
else menuTribun!!.get(position)!!.replace(" ", "").toLowerCase(), config)!!
}
override fun getItemCount(): Int {
return menuTribun!!.size
}
init {
this.config = config
}
}
an in method onCreate i implement some code related viewpager2 and its adapter like below:
viewPager2!!.offscreenPageLimit = 1
viewPager2!!.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, config)
TabLayoutMediator(tabLayout!!, viewPager2!!, TabLayoutMediator.TabConfigurationStrategy { tab: TabLayout.Tab?, position: Int -> tab!!.text = menuTribun!!.get(position) }).attach()
But i have problem when i want to make swipeRefresh. My swipeRefresh needs current fragment to access the method inside fragment. But there is NO METHOD getItem in FragmentStateAdapter. Anyone can help me?
Create property in your adapter to save all your fragments
Save current position
Get fragment from fragment list (property) by position when you need it
Something like this:
val fragments: MutableList<Fragment> = mutableListOf()
private val config: TribunConfig?
override fun createFragment(position: Int): Fragment {
val fragment = TribunNewsFragment.newInstance(if (menuTribun!!.get(position) == "News") "home"
else menuTribun!!.get(position)!!.replace(" ", "").toLowerCase(), config)!!
fragments.add(fragment)
return fragment
}
Parent:
yourTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
val fragment = adapter.fragments[tab!!.position]
// cast fragment to your fragment class and do what you want
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
})
Also you can use when or if-else for your fragments position or create base class with methods you need and cast getting fragment to it.
Following #Oleg's answer, you can access the Fragments like this in your Adapter instead of creating a local list and adding each fragment on createFragment method:
private val fragmentList: List<TribunNewsFragment>
get() {
return fragment.childFragmentManager.fragments.filterIsInstance<TribunNewsFragment>()
}
It's important to use childFragmentManager instead of fragmentManager if you are creating the ViewPager inside another fragment.
And it survives configuration changes, regarding #lawonga's comment on the answer's topic.
I have a Pager Adapter to load server images as like below:
class ImagePagerAdapter(var imageList: List<ProductImageModel>, var fragmentManager: FragmentManager) :
FragmentPagerAdapter(fragmentManager) {
override fun getItem(position: Int): Fragment {
val fragment = ImagePagerFragment()
fragment.arguments = Bundle().apply {
putString(PAGER_IMAGE_URL, imageList[position].image)
}
return fragment
}
override fun getCount(): Int {
return imageList.size
}
}
I want to know any option to addtoBackStack(null) while creating a new Fragment inside the pagerAdapter.
The current code, automatically adding the fragments to the backstack. So it's making a huge issue while the user presses the back button. I have added another logic to filter the fragments in the back press.