Компонент архитектуры навигации - передача данных аргумента в startDestination

У меня есть действие A, которое запускает действие B и передает ему некоторые намеренные данные. Деятельность B содержит граф навигации из нового компонента архитектуры навигации. Я хочу передать эти данные намерения фрагменту startDestination в качестве аргумента, как это сделать?

12 ответов

Решение

Хорошо, я нашел решение этой проблемы благодаря Lan Lake из команды Google. Допустим, у вас есть действие A, которое запустит действие B с некоторыми намеренными данными, и вы хотите получить эти данные в startDestination, у вас есть два варианта здесь, если вы используете безопасные аргументы, что в моем случае вы могли бы сделать

StartFragmentArgs.fromBundle(requireActivity().intent?.extras)

читать аргументы из намерения. Если вы не используете безопасные аргументы, вы можете извлечь данные из пакета, который вы используете самостоятельно requireActivity().intent?.extras который вернет пакет, который вы можете использовать вместо фрагмента getArguments() метод. Вот и все, я пытаюсь, и все работает отлично.

Я думаю, что это изменилось снова с выпуском 1.0.0. И Google очень хорошо спрятал эту информацию в официальной документации. Или, по крайней мере, я изо всех сил пытался найти его, но наткнулся на него в руководстве по компоненту Миграция к навигации. О том, как передать аргументы в место назначения, упоминается здесь:

https://developer.android.com/guide/navigation/navigation-migrate

Короче

  1. Вы должны установить график навигации программно:
findNavController(R.id.main_content)
                .setGraph(R.navigation.product_detail_graph, intent.extras)
  1. Не устанавливайте график в объявлении NavHostFragment XML.
  2. Читайте дополнения со стороны получателя:
val args by navArgs<ProductDetailsArgs>()  
val productId = args.productId

Обновление: Google заявил, что официальная документация для передачи аргументов исходной цели навигации действительно отсутствует. Надеемся, что это будет добавлено в ближайшее время как часть документации по компоненту навигации.

TLDR: необходимо вручную накачать график, добавить ключи / значения в defaultArgs и установить график на navController,

Шаг 1

Документация говорит вам, чтобы установить график в <fragment> тег в вашем Activity макет. Что-то вроде:

<fragment
    android:id="@+id/navFragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    app:graph="@navigation/nav_whatever"
    app:defaultNavHost="true"
    />

УДАЛИТЬ линию, устанавливающую graph= ,

Шаг 2

В действии, которое будет отображать ваш NavHostFragment Накачайте график так:

val navHostFragment = navFragment as NavHostFragment
val inflater = navHostFragment.navController.navInflater
val graph = inflater.inflate(R.navigation.nav_whatever)

куда navFragment это идентификатор, который вы дали свой фрагмент в XML, как указано выше.

Шаг 3 [Важно!]

Создайте пакет для хранения аргументов, которые вы хотите передать startDestination фрагмент и добавить его к аргументам графа по умолчанию:

val bundle = Bundle()
// ...add keys and values
graph.addDefaultArguments(bundle)

Шаг 4

Установить график на хосте navController:

navHostFragment.navController.graph = graph

Это было исправлено в 1.0.0-alpha07, См детали.

Решение похоже на ответ Эллиота Шрока, но с использованием официального API.

Надо вручную надуть NavHostFragment или же graph

использование

NavHostFragment.create(R.navigation.graph, args)

Или же

navController.setGraph(R.navigation.graph, args)

Аргументы - это данные, которые мы хотим передать в пункт назначения.

После передачи данных в раздел начального пункта назначения из официального документа:

Сначала создайте Bundle, который содержит данные:

val bundle = Bundle().apply {
    putString(KEY, "value")
}

Затем используйте один из следующих методов, чтобы передать Bundle начальному месту назначения:

  • Если вы создаете свой NavHost программно

    NavHostFragment.create(R.navigation.graph, bundle)
    
  • В противном случае вы можете установить аргументы пункта назначения, вызвав одну из следующих перегрузок NavController.setGraph():

    navHostFragment.navController.setGraph(R.navigation.graph, bundle)
    

Тогда вам следует использовать Fragment.getArguments() для получения данных в пункте назначения.

РЕДАКТИРОВАТЬ:

Вы также можете использовать FragmentArgs вместо создания пакета вручную, что делает его более удобным и безопасным по типу:

navHostFragment.navController.setGraph(R.navigation.graph, MyFragmentArgs(arg).toBundle())

Затем во фрагменте вы можете получить аргументы как:

private val args: PodFragmentArgs by navArgs()

Убедитесь, что ваш фрагмент имеет элемент аргумента в файле navigation.xml:

<fragment
        android:id="@+id/myFragment"
        android:name="MyFragment"
        android:label="fragment_my"
        tools:layout="@layout/fragment_my">
        <argument
            android:name="argName"
            android:defaultValue="@null"
            app:argType="string"
            app:nullable="true" />
</fragment>

Итак, наконец, я нашел решение этой проблемы.

На момент написания этого ответа я использую Navigation 2.2.0-alpha01

Если вы хотите передать некоторые данные в начальную точку назначения непосредственно в качестве аргументов активности хоста, вам необходимо вручную установить график навигации хоста внутри метода onCreate() активности хоста, как показано ниже:

Получите navController:

val navController by lazy { findNavController(R.id.<your_nav_host_id>) }

Затем в активности хоста onCreate()

val bundle = Bundle()
bundle.putString("some_argument", "some_value")
navController.setGraph(R.navigation.<you_nav_graph_xml>, bundle)

Или, если вы хотите передать все дополнительные функции намерения, как и в startDestination:

navController.setGraph(R.navigation.<you_nav_graph_xml>, intent.extras)

поскольку intent.extras вернет Bundle только

Когда вы устанавливаете navGraph с помощью метода setGraph(), вам следует избегать установки атрибута app:NavGraph в определении NavHostFragment, потому что это приводит к раздуванию и установке графа навигации дважды.

Читая эти аргументы во фрагменте startDestination:

Если вы используете плагин SafeArgs (который очень рекомендуется), то в вашем фрагменте:

private val args by navArgs<DummyFragmentArgs>()

Плагин SafeArgs сгенерирует класс Args, добавив Args к имени вашего фрагмента. Например, если ваш фрагмент называетсяDummyFragment тогда SafeArgs сгенерирует класс с именем DummyFragmentArgs

где navArgs<>это функция расширения, определенная в Android KTX

Если вы не используете Android KTX, вы можете получить объект args, например:

val args = DummyFragmentArgs.fromBundle(arguments!!)

После того как вы получили объект arguments, вы можете просто получить свои аргументы:

args.someArgument

Обратите внимание, как мы прошли "some_argument" как аргумент, и мы читаем его как someArgument использование безопасных аргументов

Если вы не используете SafeArgs (хотя нет причин не использовать его), вы можете получить доступ к своим аргументам следующим образом:

arguments?.getString("some_argument")

Все это задокументировано в документации по переходу на компонент навигации здесь:https://developer.android.com/guide/navigation/navigation-migrate

После прочтения решения я сделал тот, который подходит для моих нужд, это решение предполагает, что данные, отправленные в действие, на котором размещен этот график

в пункте назначения:

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    // work around get get starting destination with activity bundle
    userId = getActivity().getIntent().getIntExtra(KEY_USER_ID, -1);
}

Add DefaultArguments больше нет в последних версиях библиотеки. Я исправил проблему так:

        val navHostFragment = fragment_navigation_onboarding as NavHostFragment
        val navController = navHostFragment.navController
        val navInflater = navController.navInflater
        val graph:NavGraph = navInflater.inflate(R.navigation.navigation_your_xml)
        val model = Model()//you might get it from another activity
        graph.addArgument("Data", NavArgument.Builder().setDefaultValue(model).build()) // This is where you pass the bundle data from Activity to StartDestination
        navHostFragment.navController.graph = graph

Так что для людей все еще борющихся с этим. Я нашел другой способ сделать это, не используя Safe-Args и шаг, используя ответ @Elliot's.

Допустим, вы получили некоторые аргументы в Деятельности B от Деятельности A, и в вашей Деятельности B есть фрагмент startDestination, который вы инициализируете контроллер Nav следующим образом:

navController = Navigation.findNavController(this, R.id.detailFragment);

из Nav Controller у вас будет доступ к вашему графику, который вы установили в XML следующим образом, и вы можете установить аргументы в defaultArguments:

navController.getGraph().addDefaultArguments(extras);

Примечание: это также обновит значения ключей, если они уже присутствуют в графе xml

Теперь в вашем фрагменте вы должны найти аргументы по умолчанию из вашего NavHostFragment, например:

Bundle defaultArguments = NavHostFragment.findNavController(this).getGraph().getDefaultArguments();

и у вас будут ценности там. Я не знаю, почему @Elliot считает это важным, но так и должно быть?

ОБНОВЛЕНИЕ альфа09: аргумент addDefault больше не поддерживается в этой версии, вы должны использовать NavArgument

Вы можете передавать данные в начальную точку назначения вашего приложения. Во-первых, вы должны явно создать Bundle, содержащий данные. Затем используйте один из следующих методов, чтобы передать Bundle в начальную точку назначения, которую вы можете установить вручную график из кода, добавить аргумент с ним и удалить app:navGraph из xml и используйте эту строку кода в действии

      navController.setGraph(R.navigation.graph, args)

Я использовал ответ Эллиота Шрока, но добавил пакет другим методом.

      navController.setGraph(R.navigation.nav_graph, intent.extras!!)
      class HadithActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_hadith)
        val navHostFragment =
            supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
        val navController = navHostFragment.navController
        navController.setGraph(R.navigation.nav_hadith, intent.extras)
    }
}

activity_hadith.xml

      <androidx.fragment.app.FragmentContainerView
    android:id="@+id/fragmentContainerView"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:defaultNavHost="true"
    app:navGraph="@navigation/nav_hadith" />