Home > Software design >  Android fragment creation without constructor params
Android fragment creation without constructor params

Time:08-06

I have a viewpager2 adapter

class SectionPager2(fragment: Fragment): FragmentStateAdapter(fragment) {

    private val fragmentList = mutableListOf<Fragment>()

    override fun getItemCount() = fragmentList.size

    override fun createFragment(position: Int) = fragmentList[position]

    fun addFragment(position: Int, fragment: Fragment) {
        fragmentList.add(position, fragment)
    }

}

I use it along with a Tablayout like this

val sectionPagerAdapter = SectionPager2(this)

for (item in gradeSectionMajorOrdered) {
            var title = ""
            fragment = HomeDetailTempFragment.newInstance(thisFragment, item.majors!!, item.gradeSectionId)
            gradeSectionIds.add(item.gradeSectionId)

            for (i in viewModel.gradeSections.value?.result!!) {
                if (i.id == item.gradeSectionId) title = i.name
            }

            majorItems.add(item.majors)
            gradeSectionId = item.gradeSectionId
            sectionPagerAdapter.addFragment(gradeSectionMajorOrdered.indexOf(item), fragment)
            fragmentTitleList.add(gradeSectionMajorOrdered.indexOf(item), title)
        
}

Here I get HomeDetailTempFragment data from a server, the problem is that when I use HomeDetailTempFragment.newInstance(thisFragment, item.majors!!, item.gradeSectionId) the code for example generates 3 the same data HomeDetailTempFragment view in viewpager enter image description herebut if I change this and remove companion object newInstance from HomeDetailTempFragment it works correctlyenter image description here and we all know what's gonna happen sometimes on some devices we get unable to intantiate fatal error.

This class does not work correctly:

@SuppressLint("ValidFragment")
class HomeDetailTempFragment : BaseFragment(), MajorRecyclerViewAdapter.IMajorRV {


    companion object {
        private lateinit var listener: OnClick
        private lateinit var items: List<MajorGrades>
        private var gradeSectionId = 0

        fun newInstance(
            listener: HomeDetailFragment,
            items: List<MajorGrades>,
            gradeSectionId: Int,
        ) = HomeDetailTempFragment().also {
                this.listener = listener
                this.gradeSectionId = gradeSectionId
                this.items = items
            }
        }


    private lateinit var mContext : Context
    private lateinit var binding: FragmentHomeDetailTempBinding

    override fun onCreateView(inflater : LayoutInflater,
                              container : ViewGroup?,
                              savedInstanceState : Bundle?) : View {
        binding = FragmentHomeDetailTempBinding.inflate(layoutInflater)
        return binding.root
    }

    override fun onViewCreated(view : View, savedInstanceState : Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        mContext = requireContext()
        
        binding.rvGradeMajor.adapter = MajorRecyclerViewAdapter(context = mContext,
                                                                majors = items,
                                                                gradeSectionId = gradeSectionId,
                                                                listener = this)

    }

    override fun onGradeMajorClick(majorItem : MajorGrades, gradeItem : MajorGrades) {
        listener.onGradeMajorClick(gradeSectionId, majorItem, gradeItem)
    }
}

If I change the class this way it works as expected

@SuppressLint("ValidFragment")
class HomeDetailTempFragment(private val listener : OnClick,
                             private val items : List<MajorGrades>,
                             private val gradeSectionId : Int)
    : BaseFragment(), MajorRecyclerViewAdapter.IMajorRV {



    private lateinit var mContext : Context
    private lateinit var binding: FragmentHomeDetailTempBinding

    override fun onCreateView(inflater : LayoutInflater,
                              container : ViewGroup?,
                              savedInstanceState : Bundle?) : View {
        binding = FragmentHomeDetailTempBinding.inflate(layoutInflater)
        return binding.root
    }

    override fun onViewCreated(view : View, savedInstanceState : Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        mContext = requireContext()

        binding.rvGradeMajor.adapter = MajorRecyclerViewAdapter(context = mContext,
                                                                majors = items,
                                                                gradeSectionId = gradeSectionId,
                                                                listener = this)

    }

    override fun onGradeMajorClick(majorItem : MajorGrades, gradeItem : MajorGrades) {
        listener.onGradeMajorClick(gradeSectionId, majorItem, gradeItem)
    }
}

CodePudding user response:

Both your approaches to creating fragments are wrong.

In the first case you write data to companion object of HomeDetailTempFragment. This is same as writing to a static variable in Java. So you get only the data you wrote last - for the last fragment.

The second case would fail as soon as Android system decided to recreate your fragment, for example on screen rotation, because it would call empty constructor. But I assume it just fails to compile.

The correct way is to use setArguments in newInstance and getArguments in onViewCreated (or onCreateView). See this question for more info, including Kotlin implementation.

However I suspect you won't be able to put List<MajorGrades> into the Bundle. Also Bundles have limited size. Instead I'd suggest using a shared ViewModel for the data and passing only fragment's position via the arguments.

  • Related