Child fragment of BottomsheetDialogFragment crashes after second attempt opening - android

I have a fragment which is a child of BottomsheetDialogFragment, inside that fragment I have a google map. At first when I open this fragment using arch components NavController it works fine, than if I close this fragment and than try to open it again it crashes.
This is the error log
Error inflating class fragment
Caused by: android.view.InflateException: Binary XML file line #71 in com.example.com:layout/product_location_fragment: Error inflating class fragment
Caused by: java.lang.IllegalArgumentException: Binary XML file line #71: Duplicate id 0xffffffff, tag mapFragment, or parent id 0xffffffff with another fragment for com.google.android.gms.maps.SupportMapFragment
at androidx.fragment.app.FragmentLayoutInflaterFactory.onCreateView(FragmentLayoutInflaterFactory.java:116)
Here is the fragment itself
class ProductLocationFargment : BottomSheetDialogFragment, {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.product_location_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//this also always returns null
val frag = childFragmentManager.findFragmentById(R.id.mapFragment) as? SupportMapFragment
}
}
and layout xml
<?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:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:tag="mapFragment"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="#id/leftGuideView"
app:layout_constraintEnd_toEndOf="#id/rightGuideView" />
</androidx.constraintlayout.widget.ConstraintLayout>
This is how I'm starting that fragment from another fragment
button.setOnClickListener {
val direction = AnotherFragmentDirections.actionAnotherFragmentToProductLocationFargment()
navController.navigate(direction)
}

Try adding the mapFragment to a FrameLayout manually instead of with <fragment tag, but only if if(savedInstanceState == null) { otherwise get it by tag

Related

Handle android.view.InflateException: Error inflating class fragment

I am trying to build an app with a single activity that hosts multiple fragments. I am also using Nav Graph and Data Binding.
I am getting this error which I can't find a way around it even after reviewing similar questions on StackOverflow. All I am getting is old ways about using support fragment manager and maps fragment which are not helping my issue.
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.uxstate.goldsearchkotlin/com.uxstate.goldsearchkotlin.MainActivity}: android.view.InflateException: Binary XML file line #9 in com.uxstate.goldsearchkotlin:layout/activity_main: Binary XML file line #9 in com.uxstate.goldsearchkotlin:layout/activity_main: Error inflating class fragment
Caused by: android.view.InflateException: Binary XML file line #9 in com.uxstate.goldsearchkotlin:layout/activity_main: Binary XML file line #9 in com.uxstate.goldsearchkotlin:layout/activity_main: Error inflating class fragment
Caused by: android.view.InflateException: Binary XML file line #9 in com.uxstate.goldsearchkotlin:layout/activity_main: Binary XML file line #9 in com.uxstate.goldsearchkotlin:layout/activity_main: Error inflating class fragment
Caused by: android.view.InflateException: Binary XML file line #9 in com.uxstate.goldsearchkotlin:layout/activity_main: Error inflating class fragment
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property adapter has not been initialized
at com.uxstate.goldsearchkotlin.MainListFragment.getAdapter(MainListFragment.kt:23)
at com.uxstate.goldsearchkotlin.MainListFragment.onViewCreated(MainListFragment.kt:57)
This is my NavHostFragment which is the main cause of the crash.
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
Surprisingly, if I change <fragment> to FrameLayout the app is not crashing but the layout is not working right with FrameLayout.
This is how I inflate the layout for the home fragment.
class MainListFragment : Fragment() {
private val viewModel: ListViewModel by activityViewModels()
private var _binding: FragmentMainListBinding? = null
private val binding get() = _binding!!
lateinit var adapter: MainAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentMainListBinding.inflate(inflater, container, false)
val root: View = binding.root
return root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Add click listener to floating action button
binding.fabMain.setOnClickListener {
addListItem()
}
adapter = MainAdapter(viewModel.mainList)
main_list_view.adapter = adapter
main_list_view.layoutManager = LinearLayoutManager(requireContext())
}
Need help on how to address this exception.
The error came about because I had not initialized lateinit var adapter: MainAdapter
I initialized this inside onCreateView() and the app did not crash.

android:onClick attribute is not working through data binding

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()}"/>

Saving fragment's view state

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.

CreateView on button click error - IllegalArgumentException: No view found for id

I'm trying to launch another fragment from current fragment on button click. But I see an exception thrown while launching the new fragment.
Exception:
EXCEPTION: main
Process: com..., PID: 6047
java.lang.IllegalArgumentException: No view found for id 0x7f0a01bf (com...:id/tile_providers_root_layout) for fragment TileProviderFragment{850b9b6} (4d3783b0-443c-4b70-b24b-dcda5a49e400) id=0x7f0a01bf TileProviderFragment}
at android.support.v4.app.FragmentStateManager.createView(FragmentStateManager.java:483)
...
Fragment tiles_provider_fragment.xml file:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="#+id/tile_providers_root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorPrimary">
<android.support.v7.widget.RecyclerView
android:id="#+id/tile_providers"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="16dp"
tools:listitem="#layout/tile_provider_item"
android:paddingTop="1dp"/>
</FrameLayout>
Code to launch the fragment:
val tileProviderFragment: TileProviderFragment = TileProviderFragment.createInstance()
activity?.supportFragmentManager?.beginTransaction()
?.replace(R.id.tile_providers_root_layout, tileProviderFragment, "TileProviderFragment")
?.addToBackStack(null)
?.commit()
OnCreateView of target fragment:
override fun onCreateView(
layoutInflater: LayoutInflater, viewGroup: ViewGroup?, bundle: Bundle?): View? {
super.onCreateView(layoutInflater, viewGroup, bundle)
val view = layoutInflater.inflate(R.layout.tiles_provider_fragment, viewGroup, false)
}
Thanks!
When you use activity?.supportFragmentManager, you're using the Activity's FragmentManager which means the R.id you pass in needs to be part of the Activity's layout.
If you're referencing part of a Fragment's layout, then you should be using childFragmentManager to make the fragment a child fragment, fully contained in the parent fragment.

Use DataBinding from Fragment to access views in its Activity

I am using a fragment within an activity and using data binding to bind its layout for both the activity and fragment as shown below:
activity.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"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#id/FAB_fromHomeActivity_BottomAppBarAttached"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_add_dark"
android:backgroundTint="#color/colorAccent"
app:layout_anchor="#id/BottomAppBar_fromHomeActivity_Main"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
activity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView(this, R.layout.activity)
}
fragment.kt
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_goals, container, false)
return mBinding.root
}
Is there any way to access the FAB in the activity using the binding in the fragment ? (For example something like that mBinding.parent.FAB)
I can't find any information on this. Can anyone help ?

Categories

Resources