0

I am using NavHostFragment to display fragments. When I am trying to obtain activity's FloatingActionButton in fragment application crashes.

MainActivity.kt

class MainActivity : AppCompatActivity(), ImageDrawerListDialogFragment.OnImageClickListener {
    ...

    override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main) // <-- 
            ...
    }

}

SearchFragment.kt

class SearchFragment : Fragment(), ImageDrawerListDialogFragment.OnImageClickListener {
   
private lateinit var fab: FloatingActionButton
   ...
   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View {
      _binding = FragmentSearchBinding.inflate(inflater, container, false)
      return binding.root
   }

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

     fab = (requireActivity() as MainActivity).findViewById(R.id.floatingActionButton) // <-- 
     fab.setOnSafeClickListener { requestPermission() }
     ...
   }

}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
    tools:context=".MainActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appBar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/Theme.AppName.AppBarOverlay">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/Theme.AppName.PopupOverlay">

            <androidx.appcompat.widget.SearchView
                android:id="@+id/searchView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:focusable="true"
                android:imeOptions="actionDone"
                android:inputType="text"
                app:iconifiedByDefault="false" />

        </androidx.appcompat.widget.Toolbar>

    </com.google.android.material.appbar.AppBarLayout>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/floatingActionButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:contentDescription="create new search request"
        app:layout_anchorGravity="bottom|end"
        app:srcCompat="@android:drawable/ic_input_add" />
    
    <include layout="@layout/content_main" />


</androidx.coordinatorlayout.widget.CoordinatorLayout>

content_main.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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>

nav_graph.xml

<?xml version="1.0" encoding="utf-8"?>
<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/nav_graph"
    app:startDestination="@id/SearchFragment">

    ...
</navigation>

error

Caused by: android.view.InflateException: Binary XML file line #36 in com.dmytroa.appName:layout/activity_main: Binary XML file line #18 in com.dmytroa.appName:layout/content_main: Error inflating class fragment Caused by: android.view.InflateException: Binary XML file line #18 in com.dmytroa.appName:layout/content_main: Error inflating class fragment Caused by: java.lang.NullPointerException: requireActivity() as MainActivity).findViewById(R.id.floatingActionButton) must not be null

double-beep
  • 5,031
  • 17
  • 33
  • 41
  • Start from the error message. It has the answer – Asparuh Gavrailov Jun 27 '21 at 20:26
  • Did you not get a warning in your XML that you should replace the `` tag with a FragmentContainerView? You should **always** use FragmentContainerView and **never** use the `` tag. (See [this answer](https://stackoverflow.com/a/59275182/1676363) if you were having issues with FragmentContainerView and your NavHostFragment) – ianhanniballake Jun 27 '21 at 21:13
  • Thank you, replacing fragment with FragmentContainerView solved the problem. – Dmytro Andriichuk Jun 27 '21 at 21:52

1 Answers1

1

fab = (requireActivity() as MainActivity).findViewById(R.id.floatingActionButton) // <-- fab.setOnSafeClickListener { requestPermission() }

I would suggest you not "reach up" from the Fragment into the Activity to operate on a view that exists in its layout. This creates an explicit dependency on that Activity from your Fragment which will break if you every try to use that Fragment anywhere else.

Execute logic that operates on the Activity's views within the Activity itself.

MainActivity:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main) 
    
    fab = findViewById(R.id.floatingActionButton)
    fab.setOnSafeClickListener { requestPermission() }
    ...
}
dominicoder
  • 9,338
  • 1
  • 26
  • 32
  • It's not that I was going to use this fragment anywhere else, I just initially had this button inside fragment (but using app:layout_scrollFlags="scroll|enterAlways" with cut off the button at the bottom), and I just did not want to change the code. – Dmytro Andriichuk Jun 27 '21 at 20:41
  • Sure, that makes sense. The FAB is generally an activity-level component that belongs in the top-level CoordinatorLayout. And since it lives in the activity layout, it should be managed and controlled by the Activity and / or the activity's view model. – dominicoder Jun 27 '21 at 23:44