Глубокая ссылка из уведомления - как передать данные обратно через backstack?

В моем приложении пользователь может выбрать категорию, а затем выбрать элемент в этой категории, чтобы наконец просмотреть сведения об элементе. Стандартный / прямоточный поток:

SelectCategoryFragment -> SelectItemFragment -> ViewItemDetailsFragment

При выборе категории selectedCatId передается через Bundle от SelectCategoryFragment к SelectItemFragment:

    NavController navController = Navigation.findNavController(v);
    Bundle args = new Bundle();
    args.putLong(SelectItemFragment.ARG_CATEGORY_ID, selectedCatId);
    navController.navigate(R.id.action_nav_categories_to_items, args);

SelectItemFragment затем будет использовать значение getArguments().getLong(ARG_CATEGORY_ID) для запроса и отображения соответствующих элементов из выбранной категории.

Это нормально работает. Но сейчас я пытаюсь реализовать глубокую ссылку, когда пользователи нажимают на уведомление, перескакивая их прямо на ViewItemDetailsFragment с помощью backstack, который может поднять их до SelectItemFragment, а затем до SelectCategoryFragment.

Моя проблема в том, что, как описано, SelectItemFragment зависит от ARG_CATEGORY_ID аргумента, передаваемого ему для получения / отображения его данных. Я прочитал о о глубоких ссылках и вложенные графы навигации, но на самом деле не знаю, как передать ARG_CATEGORY_ID с глубокими ссылками / backstacks.

Есть ли удобный способ передать данные из ViewItemDetailsFragment в SelectItemFragment, когда пользователь нажимает обратно?




Ответы (1)


TL; DR: проблема может быть решена с помощью вложенных графиков. Дополнительные сведения см. В этой статье.

Более длинный ответ

Давайте определим простые фрагменты FragmentRed, FragmentGreen и FragmentBlue, которые раздувают простые макеты, каждый с соответствующим цветом фона:

<?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"
    android:background="@android:color/holo_red_dark">

    <TextView
        android:id="@+id/textView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="center"
        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 FragmentRed : Fragment() {

    private val args: FragmentRedArgs by navArgs()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.red, container, false)
        view.findViewById<TextView>(R.id.textView).text = args.foo.toString()
        return view
    }
}

FragmentGreen и FragmentBlue скопированы, но заменены все цветные тексты соответствующими цветными текстами, то есть FragmentRedArgs -> FragmentBlueArgs, R.layout.red -> R.layout.blue.

Объявим основной макет активности как таковой:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:id="@+id/content"
    android:layout_height="match_parent">

    <fragment
        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/main_graph" />
</FrameLayout>

Где main_graph:

<?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"
    android:id="@+id/main_graph"
    app:startDestination="@id/fragment_red">

    <fragment
        android:id="@+id/fragment_red"
        android:name="com.playground.FragmentRed">
        <argument
            android:name="foo"
            android:defaultValue="0"
            app:argType="integer" />
        <action
            android:id="@+id/action_fragment_red_to_fragment_green"
            app:destination="@id/fragment_green" />
    </fragment>

    <navigation
        android:id="@+id/secondLevel"
        app:startDestination="@id/fragment_green">

        <fragment
            android:id="@+id/fragment_green"
            android:name="com.playground.FragmentGreen">
            <argument
                android:name="bar"
                android:defaultValue="0"
                app:argType="integer" />
            <action
                android:id="@+id/action_fragment_green_to_fragment_blue"
                app:destination="@id/fragment_blue" />
        </fragment>

        <fragment
            android:id="@+id/fragment_blue"
            android:name="com.playground.FragmentBlue">
            <argument
                android:name="zar"
                android:defaultValue="0"
                app:argType="integer" />
        </fragment>
    </navigation>

</navigation>

Теперь внутри MainActivity создадим новое уведомление, передав аргументы для каждого из фрагментов: «foo» (красный) - 1, «bar» (зеленый) - 2, «zar» (синий) - 3.

Мы ожидаем, что при нажатии на уведомление откроется синий экран с текстом 3, при обратном нажатии см. Зеленый экран с 2, а еще один щелчок должен отобразить красный экран с 1 на экране:

class MainActivity : AppCompatActivity() {

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

        val navController = findNavController(R.id.nav_host_fragment)
        val pendingIntent = navController.createDeepLink()
            .setGraph(R.navigation.main_graph)
            .setDestination(R.id.fragment_blue)
            .setArguments(bundleOf("foo" to 1, "bar" to 2, "zar" to 3))
            .createPendingIntent()

        createNotificationChannel() // outside of the scope of this answer
        val builder = NotificationCompat.Builder(this, "my_channel")
            .setContentTitle("title")
            .setContentText("content text")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(pendingIntent)
            .setSmallIcon(R.drawable.android)
            .setAutoCancel(true)
            .setChannelId("channelId")

        with(NotificationManagerCompat.from(this)) {
            notify(100, builder.build())
        }
    }

}

Вот реальное поведение на устройстве:

введите здесь описание изображения

person azizbekian    schedule 19.06.2020
comment
Спасибо, я попробую. Итак, в основном, Bundle, переданный в setArguments() при создании PendingIntent, автоматически передается в backstack? - person ban-geoengineering; 19.06.2020
comment
Я так считаю. Но, пожалуйста, не спрашивайте, что произойдет, если два разных фрагмента имеют одинаковое имя для входного аргумента. - person azizbekian; 19.06.2020