Получить аргумент из навигационного графа

У меня есть навигационный график:

<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/jobsFragment">

    <fragment
        android:id="@+id/jobsFragment"
        android:name=".JobsFragment"
        android:label="JobsFragment">

        <action
            android:id="@+id/action_jobsFragment_to_newJobDetailsActivity"
            app:destination="@id/newJobDetailsActivity" />

    </fragment>

    <fragment
        android:id="@+id/historyFragment"
        android:name=".HistoryFragment"
        android:label="HistoryFragment" />

    <fragment
        android:id="@+id/profileFragment"
        android:name=".ProfileFragment"
        android:label="ProfileFragment" />

    <activity
        android:id="@+id/newJobDetailsActivity"
        android:name=".NewJobDetailsActivity"
        android:label="activity_new_job_details"
        tools:layout="@layout/activity_new_job_details">

        <!-- This is what I want a reference to -->
        <argument
            android:name="jobId"
            app:argType="string" />

    </activity>

</navigation>

Цель: получить значение argument name, jobId newJobDetailsActivity программно, не требуя ссылки на action. С текущим API я могу установить от jobId до action без необходимости знать литерал имени строки. Это хорошо, но не идеально для случаев, когда я хочу, чтобы вызывающий абонент мог перейти к месту назначения, не зная, к какому компоненту подключен вызывающий абонент. Это легче объяснить с помощью кода. Давайте посмотрим на два сценария, которые я пытаюсь решить.

Сценарий А

Предположим, у меня есть RecyclerView.ViewHolder, в котором есть какой-то код, который при нажатии переключается с Fragment с именем JobsFragment на Activity с именем NewJobDetailsActivity.

itemView.setOnClickListener {
    itemView.findNavController().navigate(
        JobsFragmentDirections.ActionJobsFragmentToNewJobDetailsActivity(jobId)
    )
}

Это замечательно, если RecyclerView.ViewHolder когда-либо существует только в JobsFragment, но если я хочу повторно использовать его из дополнительных Fragment или Activity в будущем, мой код провалится, потому что держателю представления нужно ссылаться на JobsFragmentDirections action, чтобы передать аргумент jobId.

Сценарий Б

Предположим, мне нужно создать PendingIntent для уведомления, и я хочу сделать это с помощью API архитектуры навигации.

NavDeepLinkBuilder(context)
    .setGraph(R.navigation.main_graph)
    .setDestination(R.id.newJobDetailsActivity)
    .setArguments(Bundle().apply {
        putString("jobId", jobId)
    })
    .createPendingIntent()

В этом случае мне не нужна ссылка на action, но ситуация на самом деле хуже, потому что я не могу найти ссылку на argument name в main_graph, поэтому я вынужден указать ее с помощью строкового литерала. Фу.

tl; dr: Кто-нибудь знает, как улучшить эти два сценария? Есть ли способ программно получить ссылку на argument из навигационного графика?

Зависимости: android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha06, android.arch.navigation:navigation-ui-ktx:1.0.0-alpha06

Плагин: androidx.navigation.safeargs




Ответы (2)


Для второго сценария вы можете использовать что-то вроде этого:

val args = JobsFragmentArgs.Builder(id).build().toBundle()
NavDeepLinkBuilder(context)
    .setGraph(R.navigation.main_graph)
    .setDestination(R.id.newJobDetailsActivity)
    .setArguments(args)
    .createPendingIntent()

Для первого сценария лично я бы рекомендовал передать прослушиватель щелчка из фрагмента / действия, который создает адаптер, и установить прослушиватель для держателя представления при привязке. Затем вы можете реализовать по клику по-разному для других фрагментов / действий.

person Emre Eran    schedule 10.10.2018
comment
Привет, Эмре. Я хочу получить ссылку на аргумент без необходимости ссылки на компонент, такой как фрагмент, в навигационном графе. Мое уведомление не имеет ничего общего с JobsFragment, поэтому я не хочу, чтобы оно упоминалось в уведомлении. Что касается прослушивателя щелчков, я понимаю, что передача прослушивателя щелчков является альтернативным решением, но это добавляет сложности. Я надеюсь избежать дополнительных сложностей, если это возможно. - person Ryan; 11.10.2018
comment
Тогда извините, я думаю, что в текущей версии навигации это невозможно, так как вы не можете назначать идентификаторы аргументам в своем навигационном графе. Вы можете получать только пункты назначения, поэтому вам нужно использовать строковый литерал. - person Emre Eran; 11.10.2018
comment
Между прочим, извините за использование JobsFragmentArgs в качестве примера NewJobDetailsActivityArgs было бы более подходящим для вашего вопроса. - person Emre Eran; 11.10.2018
comment
О да! Это действительно работает. Это не лучший способ получить аргументы адресата, но он решает проблемы в сценариях, о которых я упоминал выше. Спасибо! - person Ryan; 11.10.2018

Как упомянул Эмре в своем ответе, существует построитель args, который можно вызвать из сгенерированного класса назначения *Args, который затем может быть построен с аргументом (ами) и преобразован в Bundle. Способ сделать это не самый красивый, но он достигает того, чего я пытаюсь достичь. Обновленные реализации:

Сценарий А

itemView.setOnClickListener {
    itemView.findNavController().navigate(
        R.id.newJobDetailsActivity,
        NewJobDetailsActivityArgs.Builder(jobId)
            .build()
            .toBundle()
    )
}

Сценарий Б

NavDeepLinkBuilder(context)
    .setGraph(R.navigation.main_graph)
    .setDestination(R.id.newJobDetailsActivity)
    .setArguments(
        NewJobDetailsActivityArgs.Builder(jobId)
            .build()
            .toBundle()
    )
    .createPendingIntent()

Обновление: нажатие на уведомление с PendingIntent, созданным с NavDeepLinkBuilder, на самом деле не работает. Аргумент не передается адресату Activity. Хотите знать, не ошибка ли это в системе навигации. Отслеживание этого вопроса здесь.

person Ryan    schedule 10.10.2018