Android viewpager2 tab layout with FragmentStateAdapter - android

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

Related

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

How to fix "java.lang.IllegalStateException: Fragment already added" in Android Kotlin

I'm trying to display my RecyclerView into a fragment, which is inside a TabLayout.
I am new to this and don't get what to do for fixing it. While debugging I get:
java.lang.IllegalStateException: Fragment already added.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewPager: ViewPager = findViewById(R.id.view_pager)
val tabs: TabLayout = findViewById(R.id.tabs)
viewPager.adapter = SectionsPagerAdapter(this, supportFragmentManager)
tabs.setupWithViewPager(viewPager)
MainActivity
override fun getItem(position: Int): Fragment {
return when (position) {
0 -> FragIn()
1 -> FragProd()
2 -> FragShelf()
else -> FragIn()
}
}
getItem()-function in my PagerAdapter
override fun getItem(position: Int): Fragment {
return when (position) {
0 -> FragIn()
1 -> FragProd()
2 -> FragShelf()
else -> FragIn()
}
}
Just check what is in the method of 'FragIn()', you will get a repeated fragment if there is not a new fragment.

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

In tab layout fragments not showing their layout in androidX

I have a tab layout in which I have included five fragments, but when i click on tabs, the fragments layout is not showed. In my adapter I've extended FragmentPagerAdapter which has FragmentManager and a variable behaviour.
class ViewPagerAdapter(#NonNull fm:FragmentManager, behaviour:Int):
FragmentPagerAdapter(fm, behaviour) {
private val tabs:Array<Fragment> = arrayOf(
Category1Fragment(),
Category2Fragment(),
Category3Fragment(),
Category4Fragment(),
Category5Fragment()
)
#NonNull
override fun getItem(position: Int): Fragment {
return tabs[position]
}
override fun getCount(): Int {
return tabs.size
}
#Nullable
override fun getPageTitle(position: Int): CharSequence? {
return when (position) {
0 -> "Bags"
1 -> "Watches"
2 -> "Shoes"
3 -> "Glasses"
4 -> "Audio"
else -> null
}
}
}
and in my activity I've called adapter through viewpager, but when I debug is telling me that adapter is null, here's my activity code:
val adapter = ViewPagerAdapter(supportFragmentManager, tab_layout.selectedTabPosition)
val viewPager = findViewById<ViewPager>(R.id.view_pager)
viewPager.adapter = adapter
val tab = findViewById<TabLayout>(R.id.tab_layout)
tab.setupWithViewPager(view_pager)
// -------------------------------------------------------------------------------- //
tab.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabReselected(p0: TabLayout.Tab?) {
}
override fun onTabUnselected(p0: TabLayout.Tab?) {
}
override fun onTabSelected(p0: TabLayout.Tab?) {
viewPager.currentItem = tab.selectedTabPosition
}
})
In the previous versions of android I did the same, but in androidX things are something different. I tried also to use ViewPager2, but it was confusing.
Any help is appreciated!

fragment inside fragment {Kotlin}

is there any possible way to put a fragment inside a fragment?
I tried to put a viewPager in a fragment and write the code on OnCreatedView but it gives me error!!!
I tried this one but the fragments goes for every fragment in the activity!
pageradapter.kt
class pagerAdapter(fm:FragmentManager):FragmentStatePagerAdapter(fm){
override fun getItem(position: Int): Fragment? {
return when(position){
0-> Fragment1()
1-> Fragment2()
else -> null
}
}
override fun getCount(): Int {
return 2
}
fun rotatePosition(position: Int):Int{
return (count -1)-position
}
class pagerAdapter(fm:FragmentManager):FragmentStatePagerAdapter(fm){
override fun getItem(position: Int): Fragment? {
return when(position){
0-> Fragment1()
1-> Fragment2()
else -> null
}
}
override fun getCount(): Int {
return 2
}
fun rotatePosition(position: Int):Int{
return (count -1)-position
}
pageradapter2.kt
class pagerAdapter2(fm: FragmentManager):FragmentStatePagerAdapter(fm){
override fun getItem(position: Int): Fragment? {
return when(position){
0-> BlankFragment()
else->null
}
}
/**
* Return the number of views available.
*/
override fun getCount(): Int {
return 1
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val pagerAdapter2 = pagerAdapter2(supportFragmentManager)
val pager1= findViewById<View>(R.id.pager2) as ViewPager
pager1.adapter = pagerAdapter2
val adapter = pagerAdapter(supportFragmentManager)
val pager = findViewById<View>(R.id.pager) as ViewPager
pager.adapter = adapter
pager.setCurrentItem(adapter.rotatePosition(0), false)
}
P.S. The first Answer solved my problem.
If you are adding fragment for an activity you use either fragmentManager or supportFragmentManager.
If you are adding fragment for a fragment - you should use childFragmentManager, accessing fragmentManager from fragment will lead to using the same one that activity does.

Categories

Resources