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

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()

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:

View Pager 2 swipe between tabs doesn't work

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.

android - How to replace already opened fragment with another on item click? (Kotlin)

I have a settings screen which has a FrameLayout that loads a fragment containing a RecyclerView. The RecyclerView has selectable items, which are handled by the onItemClick function inside the fragment.
The objective, is to replace the current opened fragment with another one.
Is it possible to replace the current fragment while in the onItemClick function? If so, how?
Code
Fragment used for handling the RecyclerView and the onItemClick function
class FragmentSettingsMain : Fragment(), AdapterSettings.OnItemClickListener {
lateinit var settingsList: List<DataItemsSettings>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
settingsList = listOf(
DataItemsSettings(getString(R.string.look), getString(R.string.lookdescription), R.drawable.ic_colored_color_lens),
DataItemsSettings(getString(R.string.playing), getString(R.string.playingdescription), R.drawable.ic_colored_view_carousel),
DataItemsSettings(getString(R.string.images), getString(R.string.imagesdscription), R.drawable.ic_colored_image),
DataItemsSettings(getString(R.string.audio), getString(R.string.audiodescription), R.drawable.ic_colored_volume_up),
DataItemsSettings(getString(R.string.other), getString(R.string.otherdescription), R.drawable.ic_colored_shape),
DataItemsSettings(getString(R.string.about), getString(R.string.aboutdescription), R.drawable.ic_colored_info)
)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_settings_main, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
rvSettings.apply {
layoutManager = LinearLayoutManager(activity)
adapter = AdapterSettings(settingsList, this#FragmentSettingsMain)
}
}
override fun OnItemClick(position: Int) {
when(position) {
0 -> //NEED TO REPLACE FRAGMENT HERE
1 -> //NEED TO REPLACE FRAGMENT HERE
2 -> //NEED TO REPLACE FRAGMENT HERE
3 -> //NEED TO REPLACE FRAGMENT HERE
4 -> //NEED TO REPLACE FRAGMENT HERE
5 -> this.startActivity(Intent(context, ActivityAbout::class.java))
}
}
}
Activity where the FrameLayout is
class ActivitySettings : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
topToolbarBack.setNavigationOnClickListener {
finish()
}
val frgSettingsMain = FragmentSettingsMain()
setCurrentFragment(frgSettingsMain)
}
private fun setCurrentFragment(fragment: Fragment) =
supportFragmentManager.beginTransaction().apply {
replace(R.id.framelayoutSettings, fragment)
commit()
}
}
You can try this
override fun OnItemClick(position: Int) {
when(position) {
0 -> addContentFragment(yourFragment(),true)
1 -> //NEED TO REPLACE FRAGMENT HERE
2 -> //NEED TO REPLACE FRAGMENT HERE
3 -> //NEED TO REPLACE FRAGMENT HERE
4 -> //NEED TO REPLACE FRAGMENT HERE
5 -> this.startActivity(Intent(context, ActivityAbout::class.java))
}
}
fun addContentFragment(fragment: androidx.fragment.app.Fragment?, addToBackStack: Boolean) {
activity?.let {it->
if (!it.isFinishing) {
if (fragment == null) {
return
}
it.supportFragmentManager.let {it1->
val fragmentManager =it.supportFragmentManager
val currentFragment =
fragmentManager.findFragmentById(R.id.framelayoutSettings)
if (currentFragment != null && fragment.javaClass.isAssignableFrom(
currentFragment.javaClass
)
) {
return
}
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.add(
R.id.framelayoutSettings,
fragment,
fragment.javaClass.name
)
if (addToBackStack) {
fragmentTransaction.addToBackStack(fragment.javaClass.name)
}
fragmentTransaction.commit()
}
}
}
}

ViewPager not loading Pages after the first time I navigate to the ViewPager

I have a Fragment that contains a ViewPager. When I navigate to this fragment I see the first page as expected. However, if I hit back, and then navigate to that fragment again, I am then presented with a blank white screen...
What I'm noticing in the Layout Inspector is that when I navigate to the ViewPager Fragment the second time, the ViewPager is present, but none of the page fragments are in the hierarchy.
Here is how I set up my viewPager in my ViewPagerFragment:
/**
* Variables
*/
var pageOneFragment = ForgotPasswordInitiateFragment()
var pageTwoFragment = ForgotPasswordSubmitCodeFragment()
var pageThreeFragment = ForgotPasswordSubmitPasswordFragment()
lateinit var mViewPager: ForgotPasswordViewPager
lateinit var mPagerAdapter: ForgotPasswordPagerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.forgot_password_pager_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
forgotPasswordViewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ForgotPasswordViewModel::class.java)
pageOneFragment.viewPager = this
pageTwoFragment.viewPager = this
pageThreeFragment.viewPager = this
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activity?.let {
mPagerAdapter = ForgotPasswordPagerAdapter(it.supportFragmentManager)
}
mViewPager = viewPager
mViewPager.adapter = mPagerAdapter
mViewPager.setListeners()
}
override fun onResume() {
super.onResume()
}
inner class ForgotPasswordPagerAdapter(fm: FragmentManager): FragmentPagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
when (position) {
0 -> return pageOneFragment
1 -> return pageTwoFragment
2 -> return pageThreeFragment
else -> throw IllegalStateException("Pager position $position is out of bounds.")
}
}
override fun getCount(): Int {
return 3
}
}
I attempted to set all my mViewPager configurations into the onResume function but the same thing happened.
When does the viewPager actually populate its pages? Because what it seems like to me is they are populated the first time, but not the second....
EDIT:
I debugged the Activity's fragments on the back pressed and noticed that the pages were in that fragment stack, but not the actual viewPager fragment.
You're using
activity?.let {
mPagerAdapter = ForgotPasswordPagerAdapter(it.supportFragmentManager)
}
I.e., using the activity's FragmentManager. That's always the wrong thing to do and the source of your issue. Instead, you need to use the Fragment's childFragmentManager:
mPagerAdapter = ForgotPasswordPagerAdapter(childFragmentManager)

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
}

Categories

Resources