I have an app using Android Navigation (single activity many fragments).
When launched, the app displays a loading screen, an optional "news"-type screen which returns the user to the home screen when they're done, then a home screen.
However, I'm having trouble getting accessibility to pick up the home screen. When the news screen isn't shown, it'll get focused fine, but when it is included, the home screen will not receive focus, which seems to remain on the loading screen.
I've been able to reproduce this in a minimal example, with screens "Loading", "A", and "B". The app behaviour here is:
- Launch on Main Fragment
- wait 5 seconds, navigate to fragment A
- user presses "continue"
- return to Main Fragment
- Wait 5 seconds, navigate to fragment B
After each navigation, I'd expect the text element to be focused, and read out. However, for fragment B, it isn't.
Things I've tried:
- Using
<requestFocus />
in fragment B - Requesting focus in code
- Setting the
importantForAccessibility
state of the "Loading" fragment just before navigating away
If I change the loading fragment to use buttons for navigation, instead of the automatic behaviour, the focus does pass correctly, but obviously doesn't provide the right experience.
class LoadingFragment : Fragment() {
private lateinit var rootView: View
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View = inflater.inflate(R.layout.fragment_main, container, false).also {
rootView = it
}
private var beenHereBefore = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Mimic a delayed loading process
Handler(Looper.getMainLooper()).postDelayed({
// Navigate to the correct screen, based on state
findNavController().navigate(
if (beenHereBefore) {
R.id.action_mainFragment_to_fragmentB
} else {
beenHereBefore = true
R.id.action_mainFragment_to_fragmentA
}
)
}, 5000)
}
}
fragment_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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.main.LoadingFragment">
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MainFragment"
android:textColor="@color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
class FragmentA : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View = inflater.inflate(R.layout.fragment_a, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Button>(R.id.button).setOnClickListener {
findNavController().popBackStack()
}
}
}
fragment_a.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">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fragment A"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="308dp"
android:text="Continue"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>
class FragmentB : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View = inflater.inflate(R.layout.fragment_b, container, false)
}
fragment_b.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">
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fragment B"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
main_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/main_graph"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.example.navigationtestapp.ui.main.LoadingFragment"
android:label="fragment_main"
tools:layout="@layout/fragment_main" >
<action
android:id="@+id/action_mainFragment_to_fragmentA"
app:destination="@id/fragmentA" />
<action
android:id="@+id/action_mainFragment_to_fragmentB"
app:destination="@id/fragmentB" />
</fragment>
<fragment
android:id="@+id/fragmentA"
android:name="com.example.navigationtestapp.ui.main.FragmentA"
android:label="FragmentA" />
<fragment
android:id="@+id/fragmentB"
android:name="com.example.navigationtestapp.ui.main.FragmentB"
android:label="FragmentB" />
</navigation>