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.
Related
I have recyclerView and after click of card I would like to replace fragments in activity. The problem is I have no access to activity. Here is my code in adapter:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itemsViewModel = mList[position]
holder.tagImage.setImageResource(itemsViewModel.tagImage)
holder.tagName.text = itemsViewModel.tagName
holder.tagDescription.text = itemsViewModel.tagDescription
holder.itemView.setOnClickListener {
Log.d(InTorry.TAG, itemsViewModel.tagName)
val fragment = ProductsFragment()
val transaction = activity?.supportFragmentManager?.beginTransaction()
transaction?.replace(R.id.homeFragmentsContainer, fragment)
//transaction?.disallowAddToBackStack()
transaction?.commit()
}
}
The above replace code works in fragment but in adapter there is "activity?" error.
Kind Regards
Jack
There are multiple ways to solve this problem.
Using Context
You can use the context from holder.itemView and cast it into an Activity.
This is probably the simplest way, however this can be problematic since a Context may represent an Activity, a Service, an Application, etc. in which case it may lead to a ClassCastException when used simply.
Using Callback
You can set up a callback from your Adapter to your Activity or Fragment and then replace your Fragment.
Use JetPack Navigation
This is my personal favorite as the latest versions allow you to access NavController from Activity, Fragment or any View in the hierarchy to navigate. This is just one of many benefits of using this library.
Here is a link to Jetpack Navigation.
The Simplest and Safer way to solve this is my using Callback from your holder to activity. Below is the step by step process :
Decalare an Interface
interface OnItemClick {
fun onClick()
}
Implement that interface in you Activity and put the desired code
class MainActivity : OnItemClick {
...
override onClick() {
// Do whatever you want
val fragment = ProductsFragment()
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.homeFragmentsContainer, fragment)
transaction.commit()
}
}
Create a variable of that Interface type in you Adapter and in your onBindViewHolder method invoke that interface
class MyAdapter(val listener : OnItemClick) {
...
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
...
listener.onClick()
}
Finally pass that interface to your Adapter from you Activity
class MainActivity : OnItemClick {
val adapter = MyAdapter(this)
...
}
NOTE : Please don't pass activity to context here and there you will get unexpected result and most probably a crash.
After watching this video https://www.youtube.com/watch?v=WqrpcWXBz14
I managed to do it this way
In Adapter
class TagsAdapter(var mList: List<TagsViewModel>) :
RecyclerView.Adapter<TagsAdapter.ViewHolder>() {
var onItemClick: ((TagsViewModel) -> Unit)? = null//click listener STEP 1!!!
override fun onBindViewHolder(holder: TagsAdapter.ViewHolder, position: Int) {
holder.itemView.setOnClickListener {
onItemClick?.invoke(itemsViewModel)//click listener STEP 2!!!
}
}
}
And in Fragment
class TagsFragment : Fragment() {
private lateinit var tagsRecyclerView: RecyclerView
private var tagsArray = ArrayList<TagsViewModel>()
private lateinit var adapter: TagsAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = TagsAdapter(tagsArray)
tagsRecyclerView.adapter = adapter
adapter.onItemClick = {//click listener STEP 3!!!
val fragment = ProductsFragment()
val transaction = activity?.supportFragmentManager?.beginTransaction()
transaction?.replace(R.id.homeFragmentsContainer, fragment)
//transaction?.disallowAddToBackStack()
transaction?.commit()
}
}
}
It looks very clean and easy. I don't know is a correct way but it works
I'm using viewpager 2 now.
The views of the fragments are all the same, so I'm using the same fragments.
And the data is shown through recyclerview in the fragment.
All fragments set different data list.
OnResume retrieves new data through a network call and performs recyclerviewAdapter.submitlist.
It shows the same data view during swiping.
If onResume does not update the data, it shows a different view during swiping.
class CommunityViewPagerAdapter(
fragmentManager: FragmentManager,
lifecycle: Lifecycle,
) : FragmentStateAdapter(fragmentManager, lifecycle) {
private val fragments = listOf(
TechCommunityFragment.newInstance("Backend"),
TechCommunityFragment.newInstance("Frontend"),
TechCommunityFragment.newInstance("Android"),
TechCommunityFragment.newInstance("IOS"),
TechCommunityFragment.newInstance("DataScience"),
TechCommunityFragment.newInstance("DataAnalysis"),
TechCommunityFragment.newInstance("Algorithm"),
TechCommunityFragment.newInstance("DataStructure"),
TechCommunityFragment.newInstance("Network"),
TechCommunityFragment.newInstance("OperatingSystem"),
)
override fun getItemCount(): Int {
return fragments.size
}
override fun createFragment(position: Int): Fragment {
return fragments[position]
}
}
private fun initView() = with(binding) {
viewPager.adapter= CommunityViewPagerAdapter(childFragmentManager, lifecycle)
TabLayoutMediator(techTablayout, viewPager) { tab, position ->
tab.text = mainTabArray[position]
}.attach()
}
override fun onResume() {
super.onResume()
techStack = arguments?.getString(TECH_STACK) ?: ""
callAPI()
}
Hi guys I am using ViewPager with FragmentStateAdapter. I have an option for the user, in one of the added fragments, to recreate the activity (a method calls: activity.recreate()). However if the activity is recreated I get the following error:
java.lang.IllegalStateException: Design assumption violated.
at androidx.viewpager2.adapter.FragmentStateAdapter.placeFragmentInViewHolder(FragmentStateAdapter.java:287)
at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:276)
at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:67)
at androidx.recyclerview.widget.RecyclerView.dispatchChildAttached(RecyclerView.java:7556)
at androidx.recyclerview.widget.RecyclerView$5.addView(RecyclerView.java:860)
at androidx.recyclerview.widget.ChildHelper.addView(ChildHelper.java:107)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addViewInt(RecyclerView.java:8601)
Here is my FragmentStateAdapter:
class MainViewPagerAdapter(fragmentActivity: FragmentActivity) :
FragmentStateAdapter(fragmentActivity) {
var fragmentList: ArrayList<Fragment> = arrayListOf()
override fun getItemCount(): Int = fragmentList.size
//Put all the fragments needed based on position
override fun createFragment(position: Int): Fragment {
return fragmentList[position]
}
fun add(fragment: Fragment) {
fragmentList.add(fragment)
notifyDataSetChanged()
}
fun addByPosition(position: Int, fragment: Fragment) {
fragmentList.add(position, fragment)
notifyDataSetChanged()
}
fun remove(index: Int) {
fragmentList.removeAt(index)
notifyDataSetChanged()
}
override fun getItemId(position: Int): Long {
return fragmentList[position].hashCode().toLong()
}
override fun containsItem(itemId: Long): Boolean {
return fragmentList.contains(itemId)
}}
I am using:
implementation "androidx.viewpager2:viewpager2:1.0.0"
and I have tried the followig:
remove getItemId() -> works but I need this method to be overridden in order to retain a proper position of all the elements when add/remove a fragment from the adapter
tried to save the list of fragments inside onSaveInstanceState(outState: Bundle) and pass it back to the adapter but I get the same error.
What am I doing wrong? What is the best way to deal with this error?
So, I have one fragment, lets call it Fragment A, in which I need it to be the main host of my sharedviewmodel.
So I have it like this
class FragmentA:Fragment() {
private val model: SharedViewModel by viewModels()
}
Now, inside Fragment A, I set viewpager2 Adapter
private fun setupViewPagerWithTabLayout(
productList: MutableList<Producto>,
drinkList: MutableList<Producto>
) {
val fragmentList = listOf(
FragmentProducts.newInstance(productList),
FragmentProducts.newInstance(drinkList))
viewPager2.adapter = MyViewPageAdapter(requireActivity(),fragmentList)
val tabLayoutMediator = TabLayoutMediator(tabLayout, viewPager2,
TabLayoutMediator.TabConfigurationStrategy { tab, position ->
when (position) {
0 -> {
tab.text = "Option1"
}
1 -> {
tab.text = "Option2"
}
}
})
tabLayoutMediator.attach()
}
Here I instantiate my FragmentProduct which is inside Fragment A in a viewpager, but my adapter needs a FragmentActivity
class MyViewPageAdapter(fragmentActivity: FragmentActivity,private val fragmentList: List<FragmentProducts>) :
FragmentStateAdapter(fragmentActivity) {
override fun getItemCount(): Int {
return fragmentList.size
}
override fun createFragment(position: Int): Fragment {
return fragmentList[position]
}
}
So, from FragmentProducts that is contained inside FragmentA, I need to share data with this viewmodel, but when I do this to share data from this fragment to FragmentA
class FragmentProducts:Fragment(){
private val model: SharedViewModel by viewModels ({requireParentFragment()})
}
I get the followin error
java.lang.IllegalStateException: Fragment FragmentProducts{6953da8}
(3a2f5cbb-113d-4ed2-8f85-56e04fcea356) f0} is not a child Fragment, it
is directly attached to com.example.MainActivity#dcc2679
so , this is telling me that FragmentProducts references to my MainActivity, and thats logic because the instance of the adapter takes requireActivity() for FragmentActivity at
viewPager2.adapter = MyViewPageAdapter(requireActivity(),fragmentList)
So, I need to pass instead of requireActivity() my FragmentA as the host of this viewpager fragment but at my ViewPagerAdapter, I cant pass FragmentStatePagerAdapter or something else to pass my FragmentA as the context for that viewpager
Since Im using viewpager2, is there a way to let FragmentA host FragmentProducts viewpager so I can get it in my FragmentProducts like this
private val model: SharedViewModel by viewModels ({requireParentFragment()})
Thanks
You're using FragmentStateAdapter(fragmentActivity). Don't do that. Pass your parent Fragment to MyViewPageAdapter and pass that to FragmentStateAdapter.
class MyViewPageAdapter(
parentFragment: Fragment,
private val fragmentList: List<FragmentProducts>
) : FragmentStateAdapter(parentFragment) {
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.