Home > Net >  Kotlin App: IllegalStateException: The specified child already has a parent. You must call removeVie
Kotlin App: IllegalStateException: The specified child already has a parent. You must call removeVie

Time:10-11

I was developing an App in kotlin, which let the users publish and contract differents services from the App.

So, in the fragment where I manage the publication of a new services, I get the following error when I try to raise a custom AlertDiolog to ask for the details of the service.

IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.

The code of my fragment is the next:

package com.example.appadoskotlin2.ui.publish

import android.annotation.SuppressLint
import android.content.ContentValues
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.DialogFragment
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.appadoskotlin2.R
import com.example.appadoskotlin2.data.Service
import com.example.appadoskotlin2.databinding.FragmentPublishBinding
import com.example.appadoskotlin2.ui.adapters.AdapterPublish
import com.example.appadoskotlin2.ui.diologs.PublishDiolog
import com.example.appadoskotlin2.ui.utils.SimpleDividerItemDecoration
import com.google.firebase.database.*

class PublishFragment : Fragment(), (Service) -> Unit, PublishDiolog.ConfirmationDialogListener {

    private lateinit var publishViewModel: PublishViewModel
    private var _binding: FragmentPublishBinding? = null
    private lateinit var rvPublish: RecyclerView
    private lateinit var adapter: AdapterPublish
    //TODO("Inicialmente proporcionamos los servicios de manera local.
    // En el futuro hacerlo a traves de una API.")
    private lateinit var user_description: String
    private lateinit var linearLayoutManager: LinearLayoutManager
    // This property is only valid between onCreateView and
    // onDestroyView.
    //private val binding get() = _binding!!
    private var services = ArrayList<Service>()
    private lateinit var database: DatabaseReference
    override fun onViewStateRestored(savedInstanceState: Bundle?) {
        super.onViewStateRestored(savedInstanceState)
        user_description = savedInstanceState?.getString("user_des").toString()

    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View? {
        publishViewModel =
            ViewModelProvider(this).get(PublishViewModel::class.java)

        _binding = FragmentPublishBinding.inflate(inflater, container, false)
        val root: View = _binding!!.root

        //TODO("Cargar array services con servicios de BBDD.")
        val postListener = object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                // Get Post object and use the values to update the UI
                if(dataSnapshot != null){
                    this@PublishFragment.services = ArrayList()
                    services = dataSnapshot.getValue() as ArrayList<Service>
                }else{
                    Toast.makeText(context, "No hay ningun servicio disponible", Toast.LENGTH_LONG).show()
                }
            }

            override fun onCancelled(databaseError: DatabaseError) {
                // Getting Post failed, log a message
                Toast.makeText(context, "Operación cancelada", Toast.LENGTH_LONG).show()
                Log.w(ContentValues.TAG, "loadPost:onCancelled", databaseError.toException())
            }
        }
        database = FirebaseDatabase.getInstance().reference
        database.addValueEventListener(postListener)

        services.add(Service("Ofertar", "Carpinteria"))
        services.add(Service("Ofertar", "Fontaneria"))
        services.add(Service("Ofertar", "Electricidad"))
        services.add(Service("Ofertar", "Otros"))
        rvPublish = root.findViewById(R.id.rvPublish)
        rvPublish.addItemDecoration(SimpleDividerItemDecoration(this.context, R.drawable.line_divider))
        linearLayoutManager = LinearLayoutManager(this.context)
        rvPublish.layoutManager = linearLayoutManager
        adapter = AdapterPublish(this, services)
        rvPublish.adapter = adapter

        return root
    }


    override fun invoke(s: Service) {


        //TODO("Mostrar Alert Diolog pidiendiendo los detalles del servicio: descripción y precio")
        //TODO("Si es "Otros" permitir al usuario que introduzca la descripcion")

        //val parent = view?.parent as ViewGroup
        //parent.removeAllViewsInLayout()
        //parent.removeAllViews()
        val parent  = this.parentFragment?.requireView() as ViewGroup
        parent.removeAllViews()
        showdialog(s)
    }

    fun showdialog(s:Service){

        fragmentManager?.let {
            val confirmationDialogFragment = PublishDiolog
                .newInstance(
                    "Confirmar: "   s.type,
                    "Introduzca la descripción y precio."
                )

            //TODO("java.lang.IllegalStateException: The specified child already has a parent.
            // You must call removeView() on the child's parent first.")
            if(confirmationDialogFragment.parentFragment != null){
                (confirmationDialogFragment.parentFragment as ViewGroup).removeAllViews()
            }
            confirmationDialogFragment.setTargetFragment(this, 0)
           // confirmationDialogFragment.
            confirmationDialogFragment.show(it, "Confirm")
        }
    }

    override fun onDialogPositiveClick(dialog: DialogFragment) {
        //TODO("add to remomte DDBB la publicación del servicio.")
        //TODO("Rescatar los servicios publicados para usarlos en el ContractFragment")

        Toast.makeText(context, "Servicio publicado correctamente", Toast.LENGTH_LONG).show()
    }

    override fun onDialogNegativeClick(dialog: DialogFragment) {
        Toast.makeText(context, "Operación cancelada", Toast.LENGTH_SHORT).show()
        dialog.dismiss()
    }
    @SuppressLint("UseRequireInsteadOfGet")
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
        if (view != null) {
            val parent = view!!.parent as ViewGroup
            parent.removeAllViews()
        }
    }
}

And the custom diolog I try to raise is the following:

package com.example.appadoskotlin2.ui.diologs

import android.annotation.SuppressLint
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.text.InputType
import android.view.View
import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.example.appadoskotlin2.R
import com.example.appadoskotlin2.R.id.dialog_publish
import com.example.appadoskotlin2.ui.publish.PublishFragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputEditText

private const val TITLE = "Confirmar"
private const val DESCRIPTION = "description_param"
private const val PRICE = "20"

class PublishDiolog: DialogFragment() {
    internal lateinit var listener: ConfirmationDialogListener
    private var title: String? = null
    private var description: String? = null
    private var price: String? = null
    private lateinit var input: TextInputEditText
    private lateinit var input_price: TextInputEditText
    private lateinit var btn_publish: MaterialButton

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        try {
            listener = targetFragment as ConfirmationDialogListener
        } catch (e: ClassCastException) {
            throw ClassCastException((targetFragment.toString()   " must implement ConfirmationDialogListener"))
        }

        arguments?.let {
            title = it.getString(TITLE)
            description = it.getString(DESCRIPTION)
            price = it.getString(PRICE)
        }
    }
    //TODO("Crear custom Diolog para solicitar descripción y precio del servicio.")
    @SuppressLint("ResourceType")
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return activity?.let {
            val builder = AlertDialog.Builder(it)
            title?.let { title -> builder.setTitle(title) }

            val inflater = requireActivity().layoutInflater
            val view: View = inflater.inflate(R.layout.dialog_publish, null, false)
            builder.setView(view)
            initView(view)
            input.setHint("Descripción")
            input.inputType = InputType.TYPE_CLASS_TEXT

            input_price.setHint("€")
            input_price.inputType = InputType.TYPE_CLASS_NUMBER
            builder
                .setMessage(description)
                .setPositiveButton("Yes",
                    DialogInterface.OnClickListener { _, _ ->
                        var d_Text = input.text.toString()
                        savedInstanceState?.putString("user_des", d_Text)
                        listener.onDialogPositiveClick(this)
                    })
                .setNegativeButton("No",
                    DialogInterface.OnClickListener { _, _ ->
                        listener.onDialogNegativeClick(this)
                    })
                .setView(input)

            builder.create()
        } ?: throw IllegalStateException("Activity cannot be null")
    }

    private fun initView(view: View) {
        input = view.findViewById(R.id.description_input_text)
        input_price = view.findViewById(R.id.price_input_edit)
        btn_publish = view.findViewById(R.id.btn_publish)
    }

    interface ConfirmationDialogListener {
        fun onDialogPositiveClick(dialog: DialogFragment)
        fun onDialogNegativeClick(dialog: DialogFragment)
    }

    companion object {
        @JvmStatic
        fun newInstance(title: String?, description: String) =
            PublishDiolog().apply {
                arguments = Bundle().apply {
                    putString(TITLE, title)
                    putString(DESCRIPTION, description)
                    putString(PRICE, price)
                }
            }
    }
}

As you see I try to called the removeAllVIews() method into parent fragment but doen't work anyway.

The main point is I tryy to remove the child's parent, on differents ways an anyone works..

If you know differents options why the error comes, take thanks in advance !

[EDIT]

Added the completed logcat message of the error:

java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
        at android.view.ViewGroup.addViewInner(ViewGroup.java:5038)
        at android.view.ViewGroup.addView(ViewGroup.java:4869)
        at android.view.ViewGroup.addView(ViewGroup.java:4841)
        at androidx.appcompat.app.AlertController.setupCustomContent(AlertController.java:657)
        at androidx.appcompat.app.AlertController.setupView(AlertController.java:475)
        at androidx.appcompat.app.AlertController.installContent(AlertController.java:233)
        at androidx.appcompat.app.AlertDialog.onCreate(AlertDialog.java:279)
        at android.app.Dialog.dispatchOnCreate(Dialog.java:407)
        at android.app.Dialog.show(Dialog.java:302)
        at androidx.fragment.app.DialogFragment.onStart(DialogFragment.java:687)
        at androidx.fragment.app.Fragment.performStart(Fragment.java:3021)
        at androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:589)
        at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:300)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2100)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:2002)
        at androidx.fragment.app.FragmentManager$5.run(FragmentManager.java:524)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:201)
        at android.app.ActivityThread.main(ActivityThread.java:6861)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)

CodePudding user response:

I see that in your dialog you have two calls to builder.setView, that seems unusual. Which one do you actually want to be the view?

In the second call you pass input which is a subview of view. So that view already has a parent, and throws the exception. If you want input to be the view to display, instantiate it separate from the binding and pass it.

  • Related