'Fragment switching causes fragment to recreate itself instead

So for some reason I am unable to switch between to specific fragments and when it should switch between fragments it keeps recreating the original fragment instead of replacing it with the intended fragment.

This is my fragment from where I want to switch to a different fragment:

package ikanda.cleverspace.fragments

import android.content.Context
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import ikanda.cleverspace.R
import ikanda.cleverspace.activities.MainActivity
import ikanda.cleverspace.base.AppNavigation
import ikanda.cleverspace.models.*
import ikanda.cleverspace.ui.BookingsViewModel
import ikanda.cleverspace.ui.CompanyViewModel
import ikanda.cleverspace.ui.DeviceViewModel
import ikanda.cleverspace.ui.RoomViewModel
import ikanda.cleverspace.utils.GuiUtil
import ikanda.cleverspace.utils.PreferenceUtil
import kotlinx.android.synthetic.main.fragment_book_a_room.*
import kotlinx.android.synthetic.main.fragment_bookings.*
import java.time.*
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit


/**
 * A simple [Fragment] subclass.
 * Use the [BookingsFragment.newInstance] factory method to
 * create an instance of this fragment.
 */
class BookingsFragment : Fragment() {
    private lateinit var navigationCallback: AppNavigation

    private lateinit var deviceViewModel: DeviceViewModel
    private lateinit var bookingsViewModel: BookingsViewModel
    private lateinit var roomViewModel: RoomViewModel
    private lateinit var companyViewModel: CompanyViewModel

    private lateinit var bookings: List<Booking>
    private lateinit var currentDevice: Device
    private lateinit var currentRoom: Room
    private lateinit var currentCompany: Company

    private val runnableBookings: Runnable = object : Runnable {
        override fun run() {
            bookingsViewModel.retrieveBookings(
                currentDevice.companyId,
                currentRoom.email
            )
            txt_clock_date.text =
                LocalDate.now().format(
                    DateTimeFormatter.ofPattern(
                        "EEE dd/MM/yyyy"
                    )
                )
            //This basically reruns this runnable in 60 seconds
            handler?.postDelayed(this, 60000)
        }
    }
    private val runnableDevice: Runnable = object : Runnable {
        override fun run() {
            //deviceViewModel.retrieveDevice(PreferenceUtil.getDeviceId()!!)
            var deviceId = PreferenceUtil.getDeviceId()
            if(deviceId!=null) deviceViewModel.retrieveDevice(deviceId)
            else {
                handler?.removeCallbacksAndMessages(null)
                check_out.setOnClickListener { null }
                check_in.setOnClickListener { null }
                book_room.setOnClickListener(null)
                bookingsViewModel.bookings.removeObservers(this)
                bookingsViewModel.updateSuccess.removeObservers(this)
                roomViewModel.currentRoom.removeObservers(this)
                deviceViewModel.currentDevice.removeObservers(this)
                companyViewModel.activeCompany.removeObservers(this)
                navigationCallback.replaceFragments(ActivationFragment::class.java, "activate","bookings")
            }
            txt_clock_date.text =
                LocalDate.now().format(
                    DateTimeFormatter.ofPattern(
                        "EEE dd/MM/yyyy"
                    )
                )
            //This basically reruns this runnable every 5 minutes
            handler?.postDelayed(this, 300000)
        }
    }
    private var handler: Handler? = Handler(Looper.getMainLooper())

    override fun onAttach(context: Context) {
        super.onAttach(context)
        navigationCallback = context as AppNavigation
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        deviceViewModel = ViewModelProvider(requireActivity()).get(
            DeviceViewModel::class.java
        )
        roomViewModel = ViewModelProvider(requireActivity()).get(
            RoomViewModel::class.java
        )
        bookingsViewModel = ViewModelProvider(requireActivity()).get(
            BookingsViewModel::class.java
        )
        companyViewModel = ViewModelProvider(requireActivity()).get(
            CompanyViewModel::class.java
        )
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_bookings, container, false)
    }

    private fun initListeners() {
        txt_clock_date.text = LocalDate.now().format(
            DateTimeFormatter.ofPattern("EEE dd/MM/yyyy")
        )
        deviceViewModel.currentDevice.observe(this, {
            var deviceId = PreferenceUtil.getDeviceId()
            if(it!=null && deviceId != null) {
                currentDevice = it
                companyViewModel.getCompany(currentDevice.companyId)
            } else {
                handler?.removeCallbacksAndMessages(null)
                check_out.setOnClickListener { null }
                check_in.setOnClickListener { null }
                book_room.setOnClickListener(null)
                bookingsViewModel.bookings.removeObservers(this)
                bookingsViewModel.updateSuccess.removeObservers(this)
                roomViewModel.currentRoom.removeObservers(this)
                deviceViewModel.currentDevice.removeObservers(this)
                companyViewModel.activeCompany.removeObservers(this)
                navigationCallback.replaceFragments(ActivationFragment::class.java, "activate","bookings")
            }
        })
        companyViewModel.activeCompany.observe(this, {
            var deviceId = PreferenceUtil.getDeviceId()
            if(it!=null && deviceId!=null) {
                currentCompany = it
                roomViewModel.retrieveRoom(currentDevice.id)
            }
        })
        roomViewModel.currentRoom.observe(this, {
            // code to show room information and retrieve bookings info
        })
        bookingsViewModel.bookings.observe(this, {
            // a lot of code to show booking information
        })
        book_room.setOnClickListener {
            handler?.removeCallbacksAndMessages(null)
            check_out.setOnClickListener { null }
            check_in.setOnClickListener { null }
            book_room.setOnClickListener(null)
            bookingsViewModel.bookings.removeObservers(this)
            bookingsViewModel.updateSuccess.removeObservers(this)
            roomViewModel.currentRoom.removeObservers(this)
            deviceViewModel.currentDevice.removeObservers(this)
            companyViewModel.activeCompany.removeObservers(this)
            navigationCallback.replaceFragments(BookARoomFragment::class.java, "bookaroom","bookings")
        }
        handler?.postDelayed(runnableDevice, 300000)
        handler?.postDelayed(runnableBookings, 60000)
        deviceViewModel.retrieveDevice(PreferenceUtil.getDeviceId()!!)
    }
    override fun onStart() {
        super.onStart()
        initListeners()
    }
    override fun onStop() {
        super.onStop()
    }
    companion object {
        @JvmStatic
        fun newInstance() = BookingsFragment()
    }
}

the navigationcallback is the AppNavigation interface which the MainActivity overrides the replaceFragments method.

The Mainactivity looks like this:

package ikanda.cleverspace.activities

import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProvider
import ikanda.cleverspace.R
import ikanda.cleverspace.base.AppNavigation
import ikanda.cleverspace.constants.*
import ikanda.cleverspace.fragments.ActivationFragment
import ikanda.cleverspace.fragments.BookingsFragment
import ikanda.cleverspace.ui.DeviceViewModel
import ikanda.cleverspace.utils.DeviceUtil


class MainActivity : AppCompatActivity(), AppNavigation {

    private lateinit var activationFragment: ActivationFragment
    private lateinit var bookingsFragment: BookingsFragment
    private lateinit var deviceViewModel: DeviceViewModel
    private var uniqueID: String? = null
    private var deviceID: String? = null

    private var isActive: Boolean = false


    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        instance = this

        deviceViewModel = ViewModelProvider(this).get(DeviceViewModel::class.java)

        activationFragment = ActivationFragment.newInstance()
        bookingsFragment = BookingsFragment.newInstance()

        /**
         * show de main activity
         */
        setContentView(R.layout.activity_main)
        isActive = true

        /**
         * Check if the activity uses the layout container frame if so, add the first fragment.
         */
        val fragment = supportFragmentManager.findFragmentById(R.id.frg_ctr_main)
        if(fragment == null) {
            /**
             * otherwise we don't need to do anything en we can step out,
             * otherwise we could have layered fragments which we don't want
             */
            if(savedInstanceState != null) {
                return
            }
            deviceID = deviceViewModel.deviceId.value
            uniqueID = deviceViewModel.uuid.value
            val active = !deviceID.isNullOrEmpty()

            showBootPage()
        }
    }

    /**
     *
     */
    override fun onBackPressed() {
        if(canPop) supportFragmentManager.popBackStack()
        else finish()
    }

    /**
     * Set the initial fragment
     *
     * When device is not activated show the activationScreen
     */
    private fun showBootPage() {
        if(deviceID.isNullOrEmpty()){
            /**
             * Add the activation fragment to the layout container 'frame'
             */
            supportFragmentManager.beginTransaction().add(R.id.frg_ctr_main, activationFragment, "activate").commit()
        } else {
            /**
             * Add the bookings fragment to the layout container 'frame'
             */
            supportFragmentManager.beginTransaction().add(R.id.frg_ctr_main, bookingsFragment, "bookings").commit()
        }
    }

    override fun replaceFragments(fragmentClass: Class<*>, fragmentNewName: String, fragmentOldName: String) {
        var fragment: Fragment? = null
        try {
            fragment = fragmentClass.newInstance() as Fragment
        } catch (e: Exception) {
            e.printStackTrace()
        }
        // Insert the fragment by replacing any existing fragment
        supportFragmentManager.beginTransaction().replace(R.id.frg_ctr_main, fragment!!, fragmentNewName).addToBackStack(fragmentOldName)
            .commit()
    }

    override fun popFragment() {
        supportFragmentManager.popBackStack()
    }

    /**
     * Companian object of de [MainActivity] to make global communication possible.
     */
    companion object {
        /**
         * For global use of context
         */
        private var instance: MainActivity? = null
        /**
         * Keeps track of whether the current fragment can pop to previous fragment
         * or it can finish the application
         */
        private var canPop: Boolean = false
        /**
         * returns the [Context] of the mainactivity
         */
        fun getContext(): Context {
            return instance!!.applicationContext
        }
        /**
         * Sets the canpop variable so it can determine what action the backbutton executes
         */
        fun setCanpop(boolean: Boolean) {
            canPop = boolean
        }
    }
}

So as far as I can see there is no reason as to why my BookingsFragment would keep recreating itself in a loop, afterall it is able to switch to the BookARoomFragment without any issues so why I am unable to switch to the Activation fragment is a mystery to me.

If anyone has an idea as to what might be happening here, please let me know before i break my head on this problem since it is a really important part of the application.

Thanks in advance!



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source