View Pager 2 swipe between tabs doesn't work - android

I'm facing some problem with my ViewPager2. I have used a standard viewpager but since the FragmentPagerAdapter is deprecated I decided to migrate to ViewPager2.
That's how it looks like now
class ViewPagerAdapter(fragment : Fragment) : FragmentStateAdapter(fragment) {
override fun getItemCount(): Int {
return 3
}
override fun createFragment(position: Int): Fragment {
return when(position){
0 -> Fragment1()
1 -> Fragment2()
2 -> Fragment3()
else -> Fragment1()
}
}
class RozliczanieFragment : Fragment() {
private var _binding: FragBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragBinding.inflate(inflater, container, false)
val viewPager = binding.pager
val pagerAdapter = ViewPagerAdapter(this)
viewPager.adapter = pagerAdapter
val tabs = binding.tab
TabLayoutMediator(tabs, viewPager){
tab, position ->
when(position){
0 -> tab.text = "1"
1 -> tab.text = "2"
2 -> tab.text = "3"
}
}.attach()
return binding.root
}
My ViewPager2 is inside another fragment so i used FragmentStateAdapter and passed fragment as arg.
The problem is I can't change tab/fragment using swipe. With standard ViewPager everything was fine. What can be a problem? I couldn't find much information about the implementation of ViewPager2 inside of another fragment, so maybe I did something wrong.

Related

ViewPager2 with tabLayout not displaying all fragments

I'm using a tabLayout within a parent fragment to provide 5 child fragments to the user. I'm trying to migrate this from ViewPager to ViewPager2.
The issue is that child fragment 0 and 2 are loading, but the other 3 are blank. Using the debugger I found out that the fragment's onCreate is being called, but it seems like the fragments are instantly detached again.
All the child fragments used to load using ViewPager without any problem, therefore I think this is a ViewPager2 issue and not an issue of my child fragments.
ParentFragment:
class MoreFragment: Fragment() {
private var _binding: FragmentMoreBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
_binding = FragmentMoreBinding.inflate(inflater, container, false)
val root: View = binding.root
binding.viewPager.adapter = TabAdapterKt(requireActivity().supportFragmentManager, lifecycle)
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
tab.text = when (position) {
0 -> getString(R.string.title_clothing)
1 -> getString(R.string.title_chestloot)
2 -> getString(R.string.title_tutorials)
3 -> getString(R.string.title_locations)
4 -> getString(R.string.title_creatures)
else -> "Error"
}
}.attach()
}
}
TabAdapter:
class TabAdapterKt(fragmentManager: FragmentManager, lifecycle: Lifecycle): FragmentStateAdapter(fragmentManager, lifecycle) {
override fun createFragment(position: Int): Fragment {
when (position) {
1 -> return ChestLootFragment()
2 -> return TutorialsFragment()
3 -> return LocationsFragment()
4 -> return CreaturesFragment()
else -> return ClothingFragment()
}
}
override fun getItemCount(): Int = 5
}
EDIT:
It works if I enable the layout inspector. I can't find any solution for this. It seems to be a ViewPager2 bug.
Fragment loaded in position 0:
Fragment that failed to load in position 1:

Android viewpager2 tab layout with FragmentStateAdapter

I am using ViewPager2 with Tab Layout. Here is my MainFragment code -
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.viewPager.adapter = MyPagerAdapter(requireActivity())
TabLayoutMediator(
binding.tabLayout, binding.viewPager
) { tab, position ->
binding.viewPager.setCurrentItem(0, true)
when (position) {
0 -> tab.text = “Tab A”
1 -> tab.text = “Tab B”
}
}.attach()
}
private class MyPagerAdapter(fragmentActivity: FragmentActivity) :
FragmentStateAdapter(fragmentActivity) {
private val items = 2
override fun getItemCount(): Int {
return items
}
override fun createFragment(position: Int): Fragment = when (position) {
0 -> FragmentA()
1 -> FragmentB()
else -> FragmentA()
}
}
I have 2 questions here -
override fun createFragment(position: Int): Fragment creates new instance of child fragments every time the MainFragment view is created. Is there no way to re-use an already existing instance of child fragment?
In my Navigation graph, I have the MainFragment and its children FragmentA and FragmentB. Why cant I use the Navigation action to open children from its parent? If yes, override fun createFragment(position: Int): Fragment needs a Fragment to be returned and findNavController().navigate() does not return anything. How do I do this?
you can follow this sample code, may this will help you
class TvShowsFragment : Fragment(R.layout.tvshows_fragment) {
private var _binding: TvshowsFragmentBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = TvshowsFragmentBinding.bind(view)
setUpViewPager()
setHasOptionsMenu(true)
}
private fun setUpViewPager() {
val viewPager = binding.vpTvShows
val tab = binding.tlTvShows
val adapterTv = TvAdapter(this)
viewPager.adapter = adapterTv
TabLayoutMediator(tab, viewPager) { tabText, position ->
tabText.text = when (position) {
0 -> getString(R.string.title_tvAiringToday)
1 -> getString(R.string.title_tvOnTheAir)
2 -> getString(R.string.title_popular)
3 -> getString(R.string.title_topRated)
else -> getString(R.string.title_tvAiringToday)
}
}.attach()
}
private inner class TvAdapter(fm: Fragment) : FragmentStateAdapter(fm) {
override fun getItemCount(): Int = 4
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> TvAiringTodayFragment()
1 -> TvOnTheAirFragment()
2 -> TvPopularFragment()
3 -> TvTopRatedFragment()
else -> TvAiringTodayFragment()
}
}
}

How to add a viewPager for swiping between fragments without adding an activity

I am developing a Kotlin app and at some point I want to implement viewPager to swipe between fragments. I have one activity to navigate to the rest of the app through the navigation graph. I have not really understood how this swiping should work.
My question is, do I need to implement a new activity as well besides the pageAdapter? And how this activity is going to cooperate with the main one? My app currently has a splash screen, and after that I would like to have the swipe mode between the fragments.
I want to implement viewPager to swipe between fragments.
nice
I have one activity to navigate to the rest of the app through the navigation graph.
cool
do I need to implement a new activity as well besides the pageAdapter?
no
And how this activity is going to cooperate with the main one?
don't have a second activity, then it doesn't need to "cooperate"
I would like to have the swipe mode between the fragments.
https://gist.github.com/Zhuinden/c643f03a023a9cbe83fff6c75c948d3b
class MyFragmentPagerAdapter(
private val context: Context,
fragmentManager: FragmentManager
) : FragmentPagerAdapter(fragmentManager) {
override fun getCount() = 2
override fun getItem(position: Int) = when(position) {
0 -> FirstFragment()
1 -> SecondFragment()
else -> throw IllegalStateException("Unexpected position $position")
}
override fun getPageTitle(position: Int): CharSequence = when(position) {
0 -> context.getString(R.string.first)
1 -> context.getString(R.string.second)
else -> throw IllegalStateException("Unexpected position $position")
}
}
class ParentFragment: Fragment() {
override fun onCreateView(...) = ...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val viewPager = view.findViewById(R.id.view_pager)
viewPager.adapter = MyFragmentPagerAdapter(requireContext(), childFragmentManager)
tabLayout.setupWithViewPager(viewPager)
}
}
Try this
in your viewPager
class pageradapter (fm: FragmentManager) : FragmentStatePagerAdapter(fm){
override fun getItem(position: Int): Fragment {
when(position){
0-> return fragment1()
1-> return fragment2() // you can add more if you have more fragments
else-> return fragment3()
}
}
override fun getCount(): Int {
return 3
number of fragments that you have so the swiping could work
}
in your fragments 1 or 2 or 3 ...etc'
class fragment1 : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment1, container, false)
// write your codes
}
in your Activity after the splash screen
class MainActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main4)
val adapter = pageradapter(supportFragmentManager)
val pager = findViewById<View>(R.id.pager) as ViewPager
pager.adapter = adapter
}

Android TabLayout + ViewPager2 change tab without loading all tab in between

In my application, I have a ViewPager2 and a TabLayout. The app is composed of 4 tabs. The 3rd one dynamically loads a list fragment.
I have a problem opening the 4th tab, but only when I didn't opened the 3rd one before.
By example, this will crash:
Open app, show 1st tab. Click on 4th tab. => App will crash with following error:
java.lang.IllegalArgumentException: No view found for id 0x7f080105 (my.package:id/root_frame) for fragment ListFragment{6190d6d (6304bfad-1db2-464d-a524-7e3450243bcc) id=0x7f080105}
But if I proceed as following:
Open app, show 1st tab. Click on 3rd tab. Click on 4th tab. => App won't crash.
It looks like when I change tab, it "swipe" through all tabs between the current tab and the clicked one, causing fragments to start initialising.
Here's the code of the 3rd fragment:
class ListRootFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.list_root_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val transaction = fragmentManager!!.beginTransaction()
transaction.replace(R.id.root_frame, ListFragment.newInstance())
transaction.commit()
}
companion object {
fun newInstance(): ListRootFragment {
val fragment = ListRootFragment()
val args = Bundle()
fragment.arguments = args
return fragment
}
}
}
My ListFragment:
class ListFragment : Fragment() {
lateinit var globalData: GlobalData
private lateinit var listView: ListView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
globalData = (this.activity as ContentActivity).globalData
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View { // Inflate the layout for this fragment
val rootView: View =
inflater.inflate(R.layout.fragment_list, container, false)
listView = rootView.findViewById<ListView>(R.id.categoriesList)
val categoriesList = globalData.getCategories()
val adapter = this.getActivity()?.applicationContext?.let { CategoryAdapter(it, categoriesList) }
listView.adapter = adapter
listView.setOnItemClickListener { _, _, position, _ ->
// ...
}
return rootView
}
companion object {
fun newInstance(): ListFragment {
val fragment = ListFragment()
val args = Bundle()
fragment.arguments = args
return fragment
}
}
}
Here's the code of my ViewPagerAdapter:
class ViewPagerAdapter(fragmentActivity: FragmentActivity) :
FragmentStateAdapter(fragmentActivity) {
override fun createFragment(position: Int): Fragment {
return when(position){
0 -> HomeFragment.newInstance()
1 -> MapViewFragment.newInstance()
2 -> ListRootFragment.newInstance()
3 -> MoreFragment.newInstance()
else -> HomeFragment.newInstance()
}
}
override fun getItemCount(): Int {
return TAB_COUNT
}
companion object {
private const val TAB_COUNT = 4
}
}
And the Activity that contains the view pager:
class ContentActivity : AppCompatActivity() {
lateinit var globalData: GlobalData
lateinit var viewPager: ViewPager2
lateinit var tabLayout: TabLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_content)
globalData = applicationContext as GlobalData
viewPager = findViewById(R.id.pager)
tabLayout = findViewById(R.id.tab_layout)
viewPager.setAdapter(createCardAdapter())
viewPager.isUserInputEnabled = false
TabLayoutMediator(tabLayout, viewPager,
TabConfigurationStrategy { tab, position ->
when (position) {
0 -> tab.text = "Home"
1 -> tab.text = "Map"
2 -> tab.text = "List"
3 -> tab.text = "More"
else -> tab.text = "undefined"
}
}).attach()
}
private fun createCardAdapter(): ViewPagerAdapter? {
return ViewPagerAdapter(this)
}
}
Is there a way to prevent tabs 2+3 to load when going from 1 to 4 or, if not, how can I prevent this error ?
EDIT / WORKAROUND:
Ok so I found a workaround for this bug. Instead of using a combinaison of ViewPager2 and TabLayout, I now use a BottomNavigation for the exact same result, but without the swiping effect.
In my case it works because I only need 4 tabs, but for more complex apps with more tabs, it wouldn't work, so if anyone knows how to fix it...
To disable smoothScroll, use another TabLayoutMediator constructor:
TabLayoutMediator(tabLayout, viewPager, /*autoRefresh=*/ true, /* smoothScroll= */false
TabConfigurationStrategy { tab, position ->
//...
}
}).attach()

How can I destroy the tablayout fragments at the back event?

I have implemented a tableyout within a fragment, the logic is simple, when I click on an item in a list it derives to a fragment (tableyout), the problem happens when I go back and select another item it shows me the tableyout but with blank views, reviewing a behavior the fragments of the tablayout are never destroyed. Here is the adapter code and the main fragment. Thanks in advance.
class TabAdapter(fm: FragmentManager): FragmentPagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
return when(position){
0->FormPartOne()
1->FormPartTwo()
2->FormPartThree()
else->FormPartOne()
}
}
override fun getCount(): Int {
return 3
}
override fun getPageTitle(position: Int): CharSequence? {
return when(position){
0->"1"
1->"2"
2->"3"
else->""
}
}
}
Main Fragment:
class ReceptionFormFragment : Fragment() {
private lateinit var receptionOrderViewModel: ReceptionOrderViewModel
private lateinit var tabs: TabLayout
private lateinit var viewPager: ViewPager
private lateinit var sectionsPagerAdapter: TabAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = inflater.inflate(R.layout.fragment_reception_form, container, false)
sectionsPagerAdapter = fragmentManager?.let { TabAdapter(it) }!!
viewPager = root.findViewById(R.id.view_pager)
viewPager.adapter = sectionsPagerAdapter
tabs = root.findViewById(R.id.tabs)
tabs.setupWithViewPager(viewPager)
return root
}
}
Replace
sectionsPagerAdapter = fragmentManager?.let { TabAdapter(it) }!!
With
sectionsPagerAdapter = TabAdapter(childFragmentManager)
And it will both work correctly and remove the fragments automatically on back (when this Fragment is removed).

Categories

Resources