I am trying to show a custom dialog from a fragment. When it is about to be displayed the screen gets darker, but no dialog is presented. I also tried to show it from the activity containing the fragment, but I get the same result.
Here is the layout for my dialog:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="239dp"
android:background="#00f"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="210dp"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:background="#drawable/pop_up_shape"
android:backgroundTint="#00ff00"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
This is my dialog fragment class:
class AddPlaylistPopUp: DialogFragment(){
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.add_playlist_popup_layout, container, true)
}
}
and this is the method that should open the dialog in the activity:
fun testBut(view: View) {
val fm: FragmentManager = supportFragmentManager
val editNameDialogFragment = AddPlaylistPopUp()
editNameDialogFragment.show(fm, "fragment_edit_name")
}
in the fragment I tried like this:
fun openDialog(){
val fm = fragmentManager!!
val editNameDialogFragment = AddPlaylistPopUp()
editNameDialogFragment.show(fm, "fragment_edit_name")
}
This is the guide I followed:
https://guides.codepath.com/android/Using-DialogFragment#passing-data-to-parent-fragment
You are not creating any Dialog, you should be overriding onCreateDialog instead.
class AddPlaylistPopUp: DialogFragment(){
override fun onCreateDialog(
savedInstanceState: Bundle?
): Dialog {
val builder = AlertDialog.Builder(getContext())
builder.setTitle("Title")
builder.setView(R.layout.add_playlist_popup_layout)
return builder.create()
}
}
This way you don't event need to implement onCreateView(LayoutInflater, ViewGroup, Bundle) and you have much more control on the dialog creation.
If you would like to stick with onCreateView approach, you could fix your issue by changing the third parameter of function call LayoutInflater.inflate(resource, root, attachToRoot) by setting it to false.
EDIT:
To keep match_parent width, override Dialog size.
Related
I'm trying to inflate a custom view in a DialogFragment but the container view's are not showing up. Note I haven't found any post that matches this exactly, so if there is, please share, TIA.
here is my XML (very simple)
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="#drawable/billnotifselectiondialog"
android:layout_marginHorizontal="45dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<Button
android:id="#+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
android:background="#color/secondary_200"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
My Class:
class BillNotifSelectionDialog: DialogFragment() {
private lateinit var binding: TestDialogLayoutBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View = TestDialogLayoutBinding.inflate(LayoutInflater.from(context),container, false).apply {
binding = this
}.root
}
I'm invoking the class in my fragment with
BillNotifSelectionDialog().show(parentFragmentManager, "tag")
Here is the result, I can only see the button, not the ConstraintLayout:
This is a common issue of using ConstraintLayout as the root layout of DialogFragments.
One fix is to Replace it with RelativeLayout; but it's not recommended as per documentation quote:
For better performance and tooling support, you should instead build your layout with ConstraintLayout.
Instead of that you'd fix it programmatically by designating the dialog layout to match the parent with dialog.window.setLayout:
class BillNotifSelectionDialog : DialogFragment() {
//..... rest of code omitted
override fun onStart() {
super.onStart()
dialog!!.window!!.setLayout(MATCH_PARENT, MATCH_PARENT)
}
}
you need to override onCreateDialog instead of onCreateView in DialogFragment.
class BillNotifSelectionDialog : DialogFragment() {
private lateinit var binding: TestDialogLayoutBinding
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
binding = TestDialogLayoutBinding.inflate(layoutInflater)
return AlertDialog.Builder(requireContext())
.setView(binding.root)
.create()
}
to test the result, I changed your xml code that the background color is teal_200 and button color is purple_700
the code result
Here is my code for Fragment class.
class FragmentOne : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
// return inflater.inflate(R.layout.fragment_one, container, false)
val binding: FragmentOneBinding =
DataBindingUtil.inflate(inflater, R.layout.fragment_one, container, false)
return binding.root
}
fun onClicking(){
Toast.makeText(activity, "You clicked me.", Toast.LENGTH_SHORT).show()
}
}
And here is my code for Fragment XML.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".FragmentOne">
<data>
<variable
name="clickable"
type="com.example.fragmentpractise1.FragmentOne" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hola Gola"
android:layout_marginTop="40dp"
android:onClick="#{()-> clickable.onClicking()}"/>
</LinearLayout>
</layout>
Now what I am trying to understand, why android:onClick is not showing any toast result. On pressing the button nothing happens. I can show toast by setting onClickListener on button id in Fragment class but unable to show toast via onClick attribute in XML using databinding.
You're calling clickable.onClicking() in xml which is not set yet. When you instantiate a data binding object, you probably have to set its variables as well (like clickable in your example)
Set that variable after instantiation like this
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
// return inflater.inflate(R.layout.fragment_one, container, false)
val binding: FragmentOneBinding =
DataBindingUtil.inflate(inflater, R.layout.fragment_one, container, false)
binding.clickable = this // your fragment
return binding.root
}
Also using v instead of () inside onClick is a bit more rational because that's a lambda in Java syntax receiving one view argument. I suggest to change it to below for more readability
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hola Gola"
android:layout_marginTop="40dp"
android:onClick="#{ v -> clickable.onClicking()}"/>
I use navigation component and I faced a pretty interesting problem with fragments: whenever I open fragment B from fragment A and then go back to fragment A, fragment A's view state is lost. I mean the view is being created again. The same thing happens if I use FragmentTransaction instead of navigation component. When I detach the fragment and then attach again, the view is being created again. Is there any possible way I can prevent the view from being destroyed or is there any way I can save the state of the view?
UPDATE
I wrote a simple code to demonstrate this behavior:
activity_main.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/navHostView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.navHostView) as NavHostFragment?
val navController = navHostFragment!!.navController
navController.setGraph(R.navigation.navigation_main)
}
}
The navigation itself:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/navigation_main"
app:startDestination="#id/fragmentA">
<fragment
android:id="#+id/fragmentA"
android:name="com.sever.fragmentviewtest.FragmentA"
android:label="FragmentA"
tools:layout="#layout/fragment_a">
<action
android:id="#+id/action_fragmentA_to_fragmentB"
app:destination="#id/fragmentB" />
</fragment>
<fragment
android:id="#+id/fragmentB"
android:name="com.sever.fragmentviewtest.FragmentB"
android:label="FragmentB"
tools:layout="#layout/fragment_b" />
</navigation>
Fragment A
class FragmentA : Fragment() {
private var shouldSetText = true
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
retainInstance = true
val view = inflater.inflate(R.layout.fragment_a, container, false)
if (shouldSetText) { // Will be called only once
view.findViewById<TextView>(R.id.tvTest).text = "Some text"
shouldSetText = false
}
view.rootView.setOnClickListener {
findNavController().navigate(R.id.action_fragmentA_to_fragmentB)
}
return view
}
}
Fragment B
class FragmentB : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_b, container, false)
view.rootView.setOnClickListener {
findNavController().navigateUp()
}
return view
}
}
Important
The FragmentA's onCreateView is being called whenever I navigate back from FragmentB. And if I made some changes to FragmentA's views (like set text to a TextView) - they are lost after popping back stack from FragmentB. I tried to use navigateUp - the behavior is the same. And also retainInstance have not helped me to solve the issue.
This is supposedly a simple thing to achieve but for some reason I can't get it to work. I'm using SDK 30 emulator to test. Basically the new fragment appears but there is zero animation whatsoever. If I add a Fade() transition it works only for the fade transition. I've tried setting sharedElement callback but the methods are called only intermittently. Basically it's a simple image. You click on it and the new fragment appears. Click it again and it's gone.
Layout snippet of activity containing image
<FrameLayout
android:id="#+id/container_test"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white"
>
<ImageView
android:id="#+id/image_error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_baseline_error_24"
android:transitionName="image"
android:layout_gravity="center"
/>
</FrameLayout>
Logic to open image fragment
override fun openImageFragment(imageView: ImageView) {
val fragment = ImageFragment()
fragment.image = imageView.drawable
fragment.arguments = Bundle().also {
it.putString(ImageFragment.KEY_TRANSITION, imageView.transitionName)
}
// fragment.enterTransition = Fade()
// fragment.exitTransition = Fade()
supportFragmentManager
.beginTransaction()
.setReorderingAllowed(true)
.addSharedElement(imageView, imageView.transitionName)
.replace(R.id.container_test, fragment)
.commitNow()
}
Image fragment layout
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/black">
<ImageView
android:id="#+id/image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitCenter"
/>
</FrameLayout>
ImageFragment
class ImageFragment : Fragment() {
private lateinit var binding: FragmentImageBinding
var image: Drawable?= null
companion object {
const val KEY_TRANSITION = "TRANSITION"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val transition = TransitionInflater.from(requireContext()).inflateTransition(android.R.transition.move)
sharedElementEnterTransition = transition
sharedElementReturnTransition = transition
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentImageBinding.inflate(inflater, container, false)
binding.imageView.transitionName = arguments?.getString(KEY_TRANSITION)
binding.imageView.setImageDrawable(image)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.setOnClickListener {
requireActivity().supportFragmentManager
.beginTransaction()
.remove(this)
.commit()
}
}
}
After much trial and error, I figured out the reason.
My initial fragment was placed statically on the XML, which prevented the fragment manager from replacing it. By using the layout inspector I figured out that the new fragment was placed right below the first one.
Basically the solution is to add the first fragment programmatically so the fragment manager can manipulate it.
In my first fragment I have an EditText with a value. The problem is when I go to FragmentTwo and go back to FragmentOne the EditText (in FragmentOne) loses the text I previous inserted. I tried to save it in the onSaveInstanceState() and retrieve in the onViewStateRestored() but it seems that he does not store the information because the onSaveInstanceState() is never called and the savedInstanceState is always null.
This is my fragment replacing method
fragmentManager
?.beginTransaction()
?.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_left, R.anim.slide_out_right, R.anim.slide_in_right)
?.replace(frame, MyFragment.newInstance(), tag)
?.addToBackStack(tag)
?.commit()
This is my onSaveInstanceState() in FragmentOne:
override fun onSaveInstanceState(outState: Bundle) {
outState.putDouble(Constants.currentAmount, currentAmount)
super.onSaveInstanceState(outState)
}
This is my onViewRestored in FragmentOne:
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
if(savedInstanceState!= null) {
currentAmountSaved = savedInstanceState.getDouble(Constants.currentAmount)
}
}
This is my back press in FragmentTwo:
fragmentManager?.popBackStack()
So my goal is to save the EditText, pass to FragmentTwo, and when I go back to FragmentOne the same EditText is with the value I put when I change Fragments.
This is my xml file from the FragmentOne(The fragment I want to restore)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/contentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusableInTouchMode="true"
android:orientation="vertical">
<View
android:id="#+id/fManageFundsHeaderLayout"
android:layout_width="match_parent"
android:layout_height="250dp"
android:background="#color/colorBackgroundToolbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" />
<include
android:id="#+id/addFundsComponent"
layout="#layout/component_manage_funds"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/fManageFundsHeaderLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>
You can do this:
private var contentView: View? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
if (contentView == null) {
contentView = inflater.inflate(xxxxx)
}
(contentView?.parent as? ViewGroup)?.removeView(contentView)
return contentView
}
I think that u r using wrong restore state callback. Try to explore Bundle object from onCreate/onCreateView methods of fragment