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.