I have a single activity app which uses a settings fragment (PreferenceFragmentCompat
) with a custom layout (set via overriding the PreferenceThemeOverlay
style). From the fragment's onViewCreated
method I'm using setSupportActionBar
to reference the toolbar which is present on that layout. This works fine until it runs on API 28+, at which point the reference to toolbarSettings
is null and an error is raised.
java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.appcompat.app.ActionBar.setTitle(java.lang.CharSequence)' on a null object reference
at androidx.navigation.ui.ActionBarOnDestinationChangedListener.setTitle(ActionBarOnDestinationChangedListener.java:48)
at androidx.navigation.ui.AbstractAppBarOnDestinationChangedListener.onDestinationChanged(AbstractAppBarOnDestinationChangedListener.java:104)
at androidx.navigation.NavController.addOnDestinationChangedListener(NavController.java:204)
at androidx.navigation.ui.NavigationUI.setupActionBarWithNavController(NavigationUI.java:228)
at androidx.navigation.ui.ActivityKt.setupActionBarWithNavController(Activity.kt:74)
at androidx.navigation.ui.ActivityKt.setupActionBarWithNavController$default(Activity.kt:89)
at com.simplenotes.notes.presentation.ui.SettingsFragment.setupActionBar(SettingsFragment.kt:39)
at com.simplenotes.notes.presentation.ui.SettingsFragment.onViewCreated(SettingsFragment.kt:20)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:332)
I've seen mention of changes to custom preference fragments in this version however can't see anything specific to this behaviour. Does anyone know a solution or can point out what has been done wrong?
SettingsFragment.kt
class SettingsFragment : PreferenceFragmentCompat() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupActionBar()
}
private fun setupActionBar() {
setHasOptionsMenu(true)
val hostActivity = requireActivity() as AppCompatActivity
hostActivity.setSupportActionBar(toolbarSettings)
hostActivity.setupActionBarWithNavController(findNavController())
val actionBar = hostActivity.supportActionBar
actionBar?.title = resources.getString(R.string.title_settings)
actionBar?.setDisplayHomeAsUpEnabled(true)
}
}
fragment_settings.xml
<LinearLayout 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"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbarSettings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbarSettings"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:id="@android:id/list_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
styles.xml
<resources>
<style name="Theme.MaterialComponents.DayNight.NoActionBar.Bridge" parent="Theme.MaterialComponents.Light.NoActionBar.Bridge" />
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar.Bridge">
<item name="preferenceTheme">@style/AppTheme.PreferenceThemeOverlay</item>
</style>
<style name="AppTheme.PreferenceThemeOverlay" parent="PreferenceThemeOverlay">
<item name="android:layout">@layout/fragment_settings</item>
</style>
</resources>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.simplenotes.notes">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".presentation.ui.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Dependencies
implementation 'androidx.preference:preference:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
In case anyone else runs into this issue I found the problem.
In addition to the styles.xml
file in the question, there was also a styles-v27.xml
file which was being used to override the windowLightNavigationBar attribute...
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar.Bridge">
<item name="android:windowLightNavigationBar">false</item>
</style>
</resources>
On API v28 though this meant the preferenceTheme value wasn't getting picked up. When the settings fragment was inflated it effectively didn't have the custom layout, hence the toolbar not being created.
In styles-v27.xml
I simply repeated the preferenceTheme attribute like below and hey presto it works fine on v28 and above.
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar.Bridge">
<item name="android:windowLightNavigationBar">false</item>
<item name="preferenceTheme">@style/AppTheme.PreferenceThemeOverlay</item>
</style>
</resources>