0

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>
acrabb3
  • 429
  • 4
  • 5

1 Answers1

0

If I were you will try to handle the accessibility focus using the accessibility API and not the requestFocus. You've mentioned that the the focus seems to be held by the loading screen, so this should work due that the a11y focus will follow it, but I will try to handle that using the sendAccessibilityEvent method https://developer.android.com/reference/android/view/accessibility/AccessibilityEventSource#sendAccessibilityEvent(int)

// Moving a11y focus from btn1 to btn 2

        btn1 = (Button) findViewById(R.id.screen3_btn1);
        btn2 = (Button) findViewById(R.id.screen3_btn2);

        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                btn2.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
            }
        });

Excellent answer about this: Android - Set TalkBack accessibility focus to a specific view

Hope this helps

  • Thanks, I'm afraid that gets me to a different problem: now the item is read out twice in the "no news" route (i.e. the app navigating directly from Main to B). – acrabb3 Aug 31 '22 at 16:24