I wanna ask if there any best way to implement view pager with only one fragment in Kotlin?
Like if you have a ViewPager Adapter like below
class ViewPagerAdapter(fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager) {
val fragments:MutableList<Fragment> = ArrayList()
val titles:MutableList<String> = ArrayList()
fun addFragment(fragment: Fragment, title:String){
fragments.add(fragment)
titles.add(title)
}
override fun getItem(p0: Int): Fragment = fragments[p0]
override fun getCount(): Int = fragments.size
override fun getPageTitle(position: Int): CharSequence? {
return titles[position]
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
super.destroyItem(container, position, `object`)
}
}
Now if you have fragment, SampleFragment (say)
Just call the method with whichever fragment you want,
adapter.addFragment(SampleFragment(),"Sample Fragment")
..
..
adapter.addFragment(SampleFragment(),"Sample Fragment 2")
Create model class FragmentConfig where you going to keep parameters for fragment.
Add array of configs fragmentConfigs to your FragmentPagerAdapter
and in adapter:
override fun getItem(position: Int): Fragment {
val fragmentConfig = fragmentConfigs[position]
return UniversalListFragment.newInstance(fragmentConfig)
}
Related
I'm trying to add more fragments dynamically inside my ViewPager2 however when I call the adapter.add(MyNewFragment, position) 2 things happens, if I use notifyDataSetChange() is not showing up the fragment but if I put notifyItemInserted(position) is showing up but the app crashes with an java.lang.IllegalStateException: Fragment already added.
This is my adapter class:
class ViewPagerAdapter(
list: MutableList<Fragment>,
fm: FragmentManager,
lifecycle: Lifecycle,
//fragmentActivity: FragmentActivity
) : FragmentStateAdapter(fm, lifecycle) {
var fragmentList = list
override fun getItemCount(): Int {
return fragmentList.size
}
fun addScreen(fragment: Fragment, position: Int) {
if(!fragmentList.contains(fragment)){
fragmentList.add(position, fragment)
//notifyItemInserted(position)
notifyDataSetChanged()
}
}
override fun createFragment(position: Int): Fragment {
return fragmentList[position]
}
// I tried to use these methods that I saw here but then the viewpager is totally blank and does not load anything or do the same Fragment already added error.
/* override fun getItemId(position: Int): Long {
return fragmentList[position].id.toLong()
}
override fun containsItem(itemId: Long): Boolean = fragmentList.any { it.id.toLong() == itemId }*/
}
And this is my the screen where the viewPager is:
#AndroidEntryPoint
class ViewPagerScreen : Fragment() {
//...
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
if (!::savedViewInstance.isInitialized) {
_binding = FragmentViewPagerScreenBinding.inflate(inflater, container, false)
//....
savedViewInstance = binding.root
}
binding.viewPager.isSaveEnabled = false
return savedViewInstance
}
fun addNewScreen(){
adapter.addScreen(NewFragment(), binding.viewPager.currentItem + 1)
}
private fun initViewPager() {
binding.viewPager.offscreenPageLimit = 2
binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
}
})
adapter = ViewPagerAdapterByFragments(
mutableListOf(FirstScreen(), SecondScreen(), ThirdScreen()),
this#ViewPagerScreen.childFragmentManager,
lifecycle
)
binding.viewPager.adapter = adapter
binding.viewPager.isUserInputEnabled = false
}
}
I think I solved the problem, thanks #snachmsm for pointing me out where to look for.
What I did was quite simple, I only had to change the param list: MutableList<Fragment> to list:MutableList<Pair<String,Fragment>> where my String is my fragmentId. Then on the method createFragment return a new fragment base on my fragmentId.
Here's the code itself:
override fun createFragment(position: Int): Fragment {
return when (fragmentList!![position]!!.first) {
"firstScreen" -> return FirstScreen()
"secondScreen" ->return SecondScreen()
"thirdScreen" ->return ThirdScreen()
"newScreen" -> return NewScreen()
else -> AnotherScreen()
}
}
Also my addScreenMethod() now looks like this:
fun addScreen(newfragment: Pair<String, Fragment>, position: Int) {
val result = fragmentList.find {
it.first == newfragment.first
}
if (result == null) {
fragmentList.add(position, newfragment)
notifyItemInserted(position)
}
}
Ok, I got the following fragment with callback there witch I'm setting to ViewPager2 adapter:
class PremiumFragment : BaseFragment(), OnPageChanged {
override fun getLogTag(): String {
return TAG
}
private lateinit var pagerCounter: ImageView
private lateinit var viewPager: ViewPager2
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewPager = view.findViewById(R.id.view_pager) as ViewPager2
viewPager.adapter = ViewPagerAdapter(this)
pagerCounter = view.findViewById(R.id.counter)
}
companion object {
const val TAG = "PremiumFragment"
fun getInstance(): BaseFragment {
Log.d(TAG, "getInstance()")
val args = Bundle()
args.putInt(ARG_LAYOUT_ID, R.layout.premium_f)
val fragment =
PremiumFragment()
fragment.arguments = args
return fragment
}
}
override fun onPageChanged() {
when (viewPager.currentItem) {
0 -> pagerCounter.setImageResource(R.drawable.circle_1)
1 -> pagerCounter.setImageResource(R.drawable.circle_2)
2 -> pagerCounter.setImageResource(R.drawable.circle_3)
}
}
}
My adapter:
class ViewPagerAdapter constructor(val callback: OnPageChanged) : RecyclerView.Adapter<PagerVH>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PagerVH =
PagerVH(LayoutInflater.from(parent.context).inflate(R.layout.viewpager_f, parent, false))
override fun getItemCount(): Int = Images.values().size
override fun onBindViewHolder(holder: PagerVH, position: Int) = holder.itemView.run {
pager_container.findViewById<ImageView>(R.id.imageView)
.setImageResource(Images.values()[position].image)
}
override fun getItemViewType(position: Int): Int {
callback.onPageChanged()
return super.getItemViewType(position)
}
companion object {
enum class Images(private val value: Int, val image: Int) {
First(0, R.drawable.wow_girl),
Second(1, R.drawable.insights_girl_2),
Third(2, R.drawable.remind_girl);
}
}
}
It should set new drawable every time we swipe ViewPager, but it changes image in fragment only once an then it is not working, have completely no idea where in adapter I should call my callback for a proper behavior, please tell me where should I call this callback? Appreciate any advices!
So I found out that you should call callback from onViewDetachedFromWindow for it calls every time you swipe fragment, and it is quite logical
override fun onViewDetachedFromWindow(holder: PagerVH) {
callback.onPageChanged()
super.onViewDetachedFromWindow(holder)
}
I am first time working with Navigation UI Architecture Component
I am trying to use ViewPager with it. I wanted to pass argument to Fragments that's is added to viewpager
I didn't found any solution related to viewpager
My code
class StoriesPagerAdapter(fragmentManager: FragmentManager?)
:FragmentPagerAdapter(fragmentManager){
private val sections= arrayListOf("science","technology","business","world","movies","travel")
override fun getItem(position: Int): Fragment {
val bundle=Bundle()
bundle.putString("section",sections[position])
Navigation.createNavigateOnClickListener(R.id.action_masterFragment_to_timesListFragment,bundle)
return TimesListFragment()
}
override fun getCount(): Int {
return sections.size
}
override fun getPageTitle(position: Int): CharSequence? {
return sections[position]
}
}
My navigation graph
MasterFragment -> contains TabLayout , ViewPager
passes section key to ListFragment via FragmentPagerAdapter
ListFragment -> shows api response based on section key
DetailsFragmenst
You can pass data in fragment instance:
class StoriesPagerAdapter(fragmentManager: FragmentManager?)
:FragmentPagerAdapter(fragmentManager){
private val sections= arrayListOf("science","technology","business","world","movies","travel")
override fun getItem(position: Int): Fragment {
return TimesListFragment().newInstance(sections)
}
override fun getCount(): Int {
return sections.size
}
override fun getPageTitle(position: Int): CharSequence? {
return sections[position]
}
}
You have to manage viewPager like old method with navigation architecture according to this answer.
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.
I have Implemented TabLayout. It contains three tabs. The first tab contains Listing of data. when User clicks on list item I want to show another Fragment. I have tried but not got success. Please help for find solution
ViewPager Adapter
inner class ViewPagerAdapter(manager: FragmentManager) : FragmentPagerAdapter(manager) {
val mFragmentList: ArrayList<Fragment> = ArrayList()
private val mFragmentTitleList: ArrayList<String> = ArrayList()
override fun getItem(position: Int): Fragment {
return mFragmentList.get(position)
}
override fun getCount(): Int {
return mFragmentList.size
}
fun addFrag(fragment: Fragment, title: String) {
mFragmentList.add(fragment)
mFragmentTitleList.add(title)
}
override fun getPageTitle(position: Int): CharSequence? {
return mFragmentTitleList.get(position)
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val createdFragment = super.instantiateItem(container, position) as Fragment
mFragmentList[position] = createdFragment
return createdFragment
}
}
Fragment Adapter
class MyAdapter(private val mActivity: FragmentActivity?, private var mMyModelList: ArrayList<MyModel>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
private var mItemBinding: ItemBinding? = null
inner class MyViewHolder(val mBinding: ItemBinding) : RecyclerView.ViewHolder(mBinding.getRoot())
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val inflater = LayoutInflater.from(mActivity)
mItemBinding = DataBindingUtil.inflate(inflater, R.layout.item, null, false)
return MyViewHolder(mItemBinding!!)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val mMyModel: MyModel = mMyModelList[position]
holder.mBinding.txtTitle.setTextColor(mActivity!!.resources.getColor(R.color.light_green))
holder.mBinding.txtTitle.setText(mMyModel.lbl)
holder.mBinding.txtDateDay.setText(mMyModel.Day + " - " + mMyModel.date)
holder.mBinding.txtCircle.setBackgroundResource(R.drawable.green_circle)
holder.mBinding.constraintLayout.setOnClickListener {
var mSecondFragment: SecondFragment = SecondFragment()
//Below code is not working
mSecondFragment.onClickItem(mMyModel.id)
/*Here I want to show another Fragment*/
}
}
override fun getItemCount(): Int {
return mMyModelList.size
}
}
Fragment should open from Viewpager's Fragment.
there is no need for fragment adapter. just setup the view pager in activity.
then initiate your fragment in activity as well. There is no need on click listener in fragment adapter. Hope it helps you !
HistoryOnGoingFragment historyOnGoingFragment = new HistoryOnGoingFragment();
HistoryPastFragment historyPastFragment = new HistoryPastFragment();
private void setUpViewPager(int tabPosition) {
TabLayoutViewPagerAdapter adapter = new TabLayoutViewPagerAdapter(getFragmentManager());
adapter.addFragment(historyOnGoingFragment,"UPCOMING");
adapter.addFragment(historyPastFragment,"COMPLETED");
historyViewPager.setAdapter(adapter);
historyViewPager.setCurrentItem(tabPosition);
historyTabLayout.setupWithViewPager(historyViewPager);
}
After many tried I found custom Navigation Bar and applied changes which I needed for call nested fragment. It's working perfectly for me. Hope It'll help others too.
https://github.com/adib2149/BottomNavBar