Multiple Fragments getting created using a viewpager

Solution for Multiple Fragments getting created using a viewpager
is Given Below:

When this particular fragment is opened, data(Question Model) of second item of the list is getting inflated to ui and lifecycle methods are also getting called twice.

I have put a swipe refresh too for checking. After refreshing the corresponding data is coming.

If i swipe to 3rd or 4th position and return to the first page after some, then correct data is getting inflated too.

Pager Adapter class

import android.util.Log
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager
import com.iisc.englishgyani.fragments.*
import com.iisc.englishgyani.models.topic_details.PracticeModel

class TopicPracticeAdapter(var fragmentManager: FragmentManager, var practiceList: ArrayList<PracticeModel>, var topic_name: String, var topicId: Int): FragmentPagerAdapter(fragmentManager) {

    private val TAG = TopicPracticeAdapter::class.java.simpleName

    init {
        Log.d(TAG, "TopicPracticeAdapter: $practiceList")
        Log.d(TAG, "topic_name: $topic_name")
    }

    override fun getCount() = practiceList.size

    override fun getItem(position: Int): Fragment {
        return when {
            practiceList.get(position).type.equals("text") -> {
                FragmentTutorialText.newInstance(practiceList.get(position).tutorialModel, topic_name)
            }
            practiceList.get(position).type.equals("video") -> {
                FragmentTutorialVideo.newInstance(practiceList.get(position).tutorialModel, topic_name)
            }
            practiceList.get(position).type.equals("link") -> {
                FragmentTutorialDoc.newInstance(practiceList.get(position).tutorialModel, topic_name)
            }
            practiceList.get(position).type.equals("MCQ") -> {
                FragmentMCQ.newInstance(practiceList.get(position).questionModel, topicId)
            }
            practiceList.get(position).type.equals("FIB") -> {
                FragmentMCQ.newInstance(practiceList.get(position).questionModel, topicId)
            }
            practiceList.get(position).type.equals("DND") -> {
                return FragmentDragAndDrop().newInstance(practiceList.get(position).questionModel, topicId)
            }
            practiceList.get(position).type.equals("MTF") -> {
                FragmentMatchTheFollowing.newInstance(practiceList.get(position).questionModel, topicId)
            }
            else -> {
                FragmentTutorialText.newInstance(practiceList.get(position).tutorialModel, topic_name)
            }
        }
    }
}

FragmentDragAndDrop


import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import com.google.android.flexbox.*
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.iisc.englishgyani.adapters.SentenceAdapter
import com.iisc.englishgyani.adapters.WordsAdapter
import com.iisc.englishgyani.callbacks.DropListener
import com.iisc.englishgyani.databinding.FragmentDragAndDropBinding
import com.iisc.englishgyani.interfaces.NextFragment
import com.iisc.englishgyani.models.answer.AnswerResponse
import com.iisc.englishgyani.models.topic_details.QuestionModel
import com.iisc.englishgyani.network.ApiService
import com.iisc.englishgyani.network.ApiUtils
import com.iisc.englishgyani.utils.Constants.Companion.showNextFragment
import com.iisc.englishgyani.utils.NetworkHelper
import com.iisc.englishgyani.utils.ProgressDialogHelper
import okhttp3.MediaType
import okhttp3.RequestBody
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER

private const val ARG_QUESTION_MODEL = "questionModel"
private const val ARG_TOPIC_ID = "topicId"

private val words = ArrayList<String>()
private val sentence = mutableListOf<String>()

class FragmentDragAndDrop : Fragment() {

    private lateinit var nextFragment: NextFragment
    private var _binding: FragmentDragAndDropBinding? = null
    private val binding get() = _binding!!

    private var selectedWord = ""
    private val TAG = FragmentDragAndDrop::class.java.simpleName

    // TODO: Rename and change types of parameters
    private var questionModel: QuestionModel? = null
    private var topicId: Int? = null

    private var mAPIService: ApiService? = null
    var loader: ProgressDialogHelper? = null

    override fun onAttach(context: Context) {
        super.onAttach(context)
        try {
            nextFragment = context as NextFragment
        } catch (e: ClassCastException) {
            throw ClassCastException((context as Activity).localClassName
                    + " must implement OnButtonClickListener")
        }
        Log.d(TAG, "onAttach: ")
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(TAG, "onCreate: ")
        mAPIService = ApiUtils.getApiService()
        loader = context?.let {
            ProgressDialogHelper(it)
        }
    }

    private fun setUI(){

        Log.d(TAG, "setUI showNextFragment: $showNextFragment")

//        if(showNextFragment) {
//
//            showNextFragment = false

            activity?.actionBar?.title = questionModel?.qid.toString()

            Log.d(TAG, "setUI backStackEntryCount: ${activity?.supportFragmentManager?.backStackEntryCount}")

            binding.fragmentSwipe.isRefreshing = false
            arguments?.let {
                questionModel = it.getParcelable(ARG_QUESTION_MODEL)
                topicId = it.getInt(ARG_TOPIC_ID)
            }

            Log.d(TAG, "setUI: $questionModel")
            Log.d(TAG, "setUI: $topicId")
            Log.d(TAG, "setUI qid: ${questionModel?.qid}")

            binding.fragmentSwipe.setOnRefreshListener {
                setUI()
            }

            words.clear()
            questionModel?.choices?.forEach {
                words.add(it)
            }

            val sentenceAdapter = SentenceAdapter {
                selectedWord = it
            }.apply {
                submitList(sentence)
            }

            val wordsAdapter = WordsAdapter {
                selectedWord = it
            }.apply {
                submitList(words)
            }

            val layoutManager = FlexboxLayoutManager(context, FlexDirection.ROW, FlexWrap.WRAP).apply {
                justifyContent = JustifyContent.FLEX_START
                alignItems = AlignItems.FLEX_START
            }

//        layoutManager.orientation = GridLayoutManager.VERTICAL

            binding.tvInst.text = questionModel?.desc
            binding.tvQues.text = questionModel?.question?.get(0)?.question

            binding.rvSentence.layoutManager = layoutManager

            binding.rvSentence.adapter = sentenceAdapter

            binding.rvSentence.setOnDragListener(
                    DropListener {
                        Log.d(TAG, "setUI selectedWord: $selectedWord")
                        Log.d(TAG, "setUI sentenceAdapter: ${sentenceAdapter.currentList}")
                        Log.d(TAG, "setUI wordsAdapter: ${wordsAdapter.currentList}")
                        wordsAdapter.removeItem(selectedWord)
                        if (!sentence.contains(selectedWord)) {
                            sentenceAdapter.addItem(selectedWord)
                        }
//                    sentenceAdapter.notifyDataSetChanged()
//                    wordsAdapter.notifyItemRemoved(words.indexOf(selectedWord))
                    }
            )
            binding.rvWords.layoutManager = FlexboxLayoutManager(context, FlexDirection.ROW, FlexWrap.WRAP).apply {
                justifyContent = JustifyContent.FLEX_START
                alignItems = AlignItems.FLEX_START
            }

            binding.rvWords.adapter = wordsAdapter

            binding.rvWords.setOnDragListener(
                    DropListener {
                        sentenceAdapter.removeItem(selectedWord)
                        wordsAdapter.addItem(selectedWord)
                    }
            )

            binding.cardSubmit.setOnClickListener {
                val selectedAnswer = StringBuffer()
                sentenceAdapter.currentList.forEach {
//                selectedAnswer.commonPrefixWith(" ", sentenceAdapter.currentList.indexOf(it) == sentenceAdapter.currentList.size)
                    selectedAnswer.append("$it ")
                }
                Log.d(TAG, "setUI:${selectedAnswer.lastIndex}")
                Log.d(TAG, "setUI:${selectedAnswer.length}")
                Log.d(TAG, "setUI:${selectedAnswer.substring(0, selectedAnswer.lastIndex)}")
                val choice = ArrayList<String>()
                choice.add(selectedAnswer.substring(0, selectedAnswer.lastIndex))
                validate(choice)
            }
//        }

    }

    override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?,
    ): View? {

        Log.d(TAG, "onCreateView: ")
        _binding = FragmentDragAndDropBinding.inflate(inflater, container, false)
        return binding.root
    }

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

        setUI()

        Log.d(TAG, "onViewCreated: ")

        Log.d(TAG, "onViewCreated questionModel: $questionModel")
        Log.d(TAG, "onViewCreated topicId: $topicId")

    }

    private fun validate(choice: ArrayList<String>?) {

        // req -> {topic_id, qid, type, category, email_id, question array -> {question, user_answer}}
        // res <- {question array, type}

        try {
            if (NetworkHelper.isNetworkAvailable(context!!)) {
                submitAnswer(choice)
            } else {
                Toast.makeText(context, "Connect to internet", Toast.LENGTH_SHORT).show()
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }

    }

    private fun submitAnswer(choice: ArrayList<String>?) {

        loader?.showProgressDialog()

        val jsonObject = JSONObject()
        val question = JSONArray()
//        question.put(questionModel?.question?.get(0)?.question?.let {
//            choice?.let { choice ->
//                QAPairModel(it, choice.get(0))
//            }
//        })

        val jsonObjectQuestion = JSONObject()
        jsonObjectQuestion.put("question", questionModel?.question?.get(0)?.question)
        jsonObjectQuestion.put("user_answer", choice?.get(0))

        question.put(jsonObjectQuestion)

        try {
            val account = GoogleSignIn.getLastSignedInAccount(context)
            account?.let {
                jsonObject.put("topic_id", topicId)
                jsonObject.put("type", questionModel?.type)
                jsonObject.put("qid", questionModel?.qid)
                jsonObject.put("email_id", account.email)
                jsonObject.put("category", "grammer")
                jsonObject.put("question", question)
            }

        } catch (e: JSONException) {
            e.printStackTrace()
        }
        Log.d(TAG, "submitAnswer: json object $jsonObject")

        val body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonObject.toString())

        Log.d(TAG, "onResponse RequestBody: $body")

        mAPIService = ApiUtils.getApiService()
        mAPIService?.answer(body)?.enqueue(object : Callback<AnswerResponse> {
            override fun onResponse(call: Call<AnswerResponse>, response: Response<AnswerResponse>) {
                Log.d(TAG, "onResponse: ${response.body()}")
                if (response.isSuccessful) {
                    loader?.hideProgressDialog()
                    try {
                        Toast.makeText(context, response.body()?.message, Toast.LENGTH_SHORT).show()
                        nextFragment.showNextFragment()
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                }
            }

            override fun onFailure(call: Call<AnswerResponse>, t: Throwable) {
                loader?.hideProgressDialog()
                Log.d(TAG, "onFailure: ${t.message}")
            }

        })

    }

//    companion object {
//        /**
//         * Use this factory method to create a new instance of
//         * this fragment using the provided parameters.
//         *
//         * @param questionModel Parameter 1.
//         * @param topicId Parameter 2.
//         * @return A new instance of fragment FragmentDragAndDrop.
//         */
//        // TODO: Rename and change types and number of parameters
//        @JvmStatic
//        fun newInstance(questionModel: QuestionModel?, topicId: Int) =
//                FragmentDragAndDrop().apply {
//                    arguments = Bundle().apply {
//                        putParcelable(ARG_QUESTION_MODEL, questionModel)
//                        putInt(ARG_TOPIC_ID, topicId)
//                    }
//                }
//    }


    fun newInstance(questionModel: QuestionModel?, topicId: Int) =
            FragmentDragAndDrop().apply {
                arguments = Bundle().apply {
                    putParcelable(ARG_QUESTION_MODEL, questionModel)
                    putInt(ARG_TOPIC_ID, topicId)
                }
            }

    override fun onPause() {
        super.onPause()
        Log.d(TAG, "onPause: ")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy: ")
    }

    override fun onResume() {
        super.onResume()
        Log.d(TAG, "onResume: ")
        setUI()
    }

    override fun onDestroyView() {
        super.onDestroyView()
        Log.d(TAG, "onDestroyView: ")
    }

    override fun onStop() {
        super.onStop()
        Log.d(TAG, "onStop: ")
    }

}

By default, the left and right side of the fragment are preloaded into the ViewPager. It does not allow to set less than 1 “offscreen page limit” but in Viewpager 2 you can set minimum offscreen page limit to zero. So only current fragment will be loaded.

  • viewPager2.setOffscreenPageLimit(0);