3

got error fragment already added on my MyPagerAdapter class which extends FragmentPagerAdapter

this is my error logs

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: id.cahyowhy.tmdbmovieimplementation, PID: 8228
    java.lang.IllegalStateException: Fragment already added: MovieFragment{df79747} (01cf33b8-9eb3-4dce-9519-384ca7dd7570) id=0x7f08014e android:switcher:2131231054:0}
        at androidx.fragment.app.FragmentStore.addFragment(FragmentStore.java:67)
        at androidx.fragment.app.FragmentManager.addFragment(FragmentManager.java:1563)
        at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:405)
        at androidx.fragment.app.FragmentManager.executeOps(FragmentManager.java:2167)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1990)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1945)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1847)
        at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:413)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)

this is my pageradapter code

class MyPagerAdapter(fm: FragmentManager) :
    FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {

    private val pages = listOf(
        MovieFragment.newInstance(MovieListType.POPULAR),
        MovieFragment.newInstance(MovieListType.TOP_RATED),
        MovieFragment.newInstance(MovieListType.UPCOMING)
    )

    override fun getItem(position: Int): Fragment {
        return pages[position]
    }

    override fun getCount(): Int {
        return pages.size
    }

    override fun getPageTitle(position: Int): CharSequence? {
        return when (position) {
            0 -> "Popular"
            1 -> "Top Rated"
            else -> "Up Coming"
        }
    }
}

this is my movie fragment code

package id.cahyowhy.tmdbmovieimplementation.ui.activity.fragment

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import id.cahyowhy.tmdbmovieimplementation.databinding.MoviesFragmentResultBinding
import id.cahyowhy.tmdbmovieimplementation.ui.adapter.ItemTypeFactoryImpl
import id.cahyowhy.tmdbmovieimplementation.ui.adapter.VisitableRecyclerAdapter
import id.cahyowhy.tmdbmovieimplementation.ui.adapter.viewholder.ErrorStateItem
import id.cahyowhy.tmdbmovieimplementation.ui.adapter.viewholder.MovieItem
import id.cahyowhy.tmdbmovieimplementation.ui.base.BaseFragment
import id.cahyowhy.tmdbmovieimplementation.ui.base.BaseViewItem
import id.cahyowhy.tmdbmovieimplementation.ui.util.ext.observe
import org.koin.android.viewmodel.ext.android.viewModel

open class MovieFragment(
    private val movieListType: MovieListType
) : BaseFragment() {

    private val viewModel by viewModel<MovieFragmentViewModel>()

    private lateinit var binding: MoviesFragmentResultBinding

    private val movieAdapter by lazy {
        VisitableRecyclerAdapter(
            ItemTypeFactoryImpl(),
            ::onItemClicked
        )
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = MoviesFragmentResultBinding.inflate(
            inflater,
            container,
            false
        )

        return binding.root
    }

    override fun onResume() {
        super.onResume()
        viewModel.loadData(0, movieListType)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        with(binding) {
            recyclerView.adapter = movieAdapter
            recyclerView.setHasFixedSize(true)

            swipeRefresh.setOnRefreshListener { viewModel.loadData(0, movieListType) }
        }

        super.onViewCreated(view, savedInstanceState)
    }

    override fun observeChange() {
        observe(viewModel.loading, ::handleLoading)
        observe(viewModel.movies, ::onDataLoaded)
        observe(viewModel.toastMessage, ::showSnackbarMessage)
    }

    private fun handleLoading(status: Boolean) {
        binding.swipeRefresh.isRefreshing = status
    }

    private fun onDataLoaded(items: List<BaseViewItem>) {
        movieAdapter.submitList(items)
    }

    private fun onItemClicked(viewItem: BaseViewItem, view: View) {
        when (viewItem) {
            is MovieItem -> {
                Log.d("MovieItem", "MovieItem Click: ${viewItem.title}")
            }
            is ErrorStateItem -> {
                viewModel.loadData(0, movieListType)
            }
        }
    }

    companion object {
        fun newInstance(movieListType: MovieListType): MovieFragment =
            MovieFragment(movieListType)
    }
}

and this is when i called from MainActivity.kt

class MainActivity : BaseActivity() {
    private lateinit var binding: ActivityMainBinding;

    ....

    fun initView() {
        with(binding) {
            viewPager.adapter = MyPagerAdapter(supportFragmentManager)
            viewPagerTab.setupWithViewPager(viewPager)
        }
    }

can anyone solve this.. thanks..

Rajnish suryavanshi
  • 3,168
  • 2
  • 17
  • 23
cahyowhy
  • 553
  • 2
  • 9
  • 26
  • Can you share what's in your `BaseActivity` and your error log ? – ravi May 17 '20 at 06:07
  • @ravi i update my question.. sory my base activity was to much of code.. & on that file i dont have a viewpager code related – cahyowhy May 17 '20 at 06:23
  • No worries. Can you also share you `MovieFragment` code. I should have asked for that already. – ravi May 17 '20 at 06:27
  • @ravi updated my code – cahyowhy May 17 '20 at 06:34
  • Please have a look at my answer, try it and let me know if it does not work for you. – ravi May 17 '20 at 06:38
  • still didnt work.. @ravi class PopularFragment : MovieFragment(movieListType = MovieListType.POPULAR) { companion object { fun newInstance(): PopularFragment = PopularFragment() } } Fragment already added: PopularFragmen – cahyowhy May 17 '20 at 06:41

1 Answers1

1

The problem is MyFragment is a class and the FragmentManager is something that you get by calling supportFragmentManager. This is provided to you by the system. For the fragment manager to be able to add new fragments to the back stack, the Fragments provided to it should be unique.

Now I do not know what your use case is but calling MyFragment.newInstance() does not provide you unique classes as they are just new instances of the same MyFragment class. The error log is stating that.

To get around this you can create three different Fragment classes like PopularMoviesFragment, TopRatedMoviesFragment and UpcomingMoviesFragment and then add them to your list on the adapter class.

I know it seems like a lot of work but this way you have three different fragments that shows data for three different cases of Movies and you can have additional logic in them. And you will also not hit java.lang.IllegalStateException: Fragment already added: MovieFragment error

Your Adapter code where you call:

 private val pages = listOf(
        MovieFragment.newInstance(MovieListType.POPULAR),
        MovieFragment.newInstance(MovieListType.TOP_RATED),
        MovieFragment.newInstance(MovieListType.UPCOMING)
    )

should now look like:

 private val pages = listOf(
        PopularMoviesFragment.newInstance(MovieListType.POPULAR),
        TopRatedMoviesFragment.newInstance(MovieListType.TOP_RATED),
        UpcomingMoviesFragment.newInstance(MovieListType.UPCOMING)
    )
ravi
  • 899
  • 8
  • 31
  • @cahyowhy Please extend `PupularMoviesFragment` from `BaseFragment()` and not from `MovieFragment()` – ravi May 17 '20 at 06:44
  • BTW: this one asks for factory – Marcin Orlowski May 17 '20 at 20:57
  • hmm this is valid.. @ravi but how i can use moviefragment as parent class .. so i don't duplicate my code – cahyowhy May 18 '20 at 04:35
  • @cahyowhy It can look like code duplication now. But if you want separate UI and functionalities within each screen, I would say that this is a necessary devil. But if you want to use a single fragment and pass it different lists, I think you can pass three List to the Adapter and the number of pages ie. 3 in the constructor. – ravi May 18 '20 at 05:32
  • An Adapter that extends FragmentStatePagerAdapter can have ` @Override public Fragment getItem(int position) { Bundle b = new Bundle(); //figure out a way to pass the list of movies onto the bundle: use position to determine which list gets passed Fragment frag = DynamicFragment.newInstance(); frag.setArguments(b); return frag; } ` – ravi May 18 '20 at 05:35