Как установить целевой фрагмент диалога при использовании компонентов навигации

Я показываю диалог внутри фрагмента, используя childFragmentManager, или внутри действия, используя supportFragmentManager, в процессе я хотел бы установить целевой фрагмент, например так:

val textSearchDialog = TextSearchDialogFragment.newInstance()
textSearchDialog.setTargetFragment(PlaceSearchFragment@this, 0)

Но при запуске этого кода я получаю сообщение об ошибке:

java.lang.IllegalStateException: Fragment TextSearchDialogFragment{b7fce67 #0 0} объявил целевой фрагмент PlaceSearchFragment{f87414 #0 id=0x7f080078}, который не принадлежит этому FragmentManager!

Я не знаю, как получить доступ к FragmentManager компонентам навигации, которые используют для управления отображением фрагмента, есть ли решение для этого?


comment
похоже, проблема в том, что вам нужно использовать тот же диспетчер фрагментов вместо дочернего, поэтому используйте getFragmentManager()   -  person Pavneet_Singh    schedule 08.06.2018


Ответы (4)


Обновление: в Навигации 2.3.0 добавлена ​​явная поддержка возвращает результат с определенным разделом возвращает результат назначения Dialog в качестве альтернативы использованию общей ViewModel.

Предыдущий ответ:

Рекомендуемый шаблон для связи между фрагментами с компонентами архитектуры навигации — через общий ViewModel< /a> — ViewModel, который живет на уровне активности, полученном путем получения ViewModel с помощью ViewModelProvider(getActivity())

Согласно документации, это дает ряд преимуществ:

  • Деятельности не нужно ничего делать или знать что-либо об этом общении.
  • Фрагменты не должны знать друг о друге, кроме контракта SharedViewModel. Если один из фрагментов исчезает, другой продолжает работать в обычном режиме.
  • Каждый фрагмент имеет свой жизненный цикл и не зависит от жизненного цикла другого. Если один фрагмент заменяет другой, UI продолжает работать без проблем.

Вы также можете делиться ViewModels в меньшей области, чем вся ваша деятельность, используя навигационный граф с областью действия ViewModel.

person ianhanniballake    schedule 08.06.2018
comment
Отлично, позже немного подумал об этом. Спасибо что подметил это. - person Eury Pérez Beltré; 08.06.2018
comment
Я думаю, что это неправильный ответ. Ни в коем случае вы не захотите создавать общую ViewModel, которая будет жить на протяжении всего жизненного цикла родительской Activity только для связи с вашим диалогом. На самом деле в навигационной среде отсутствует добавление целевых фрагментов. - person konata; 23.01.2019
comment
Shared ViewModel звучит неправильно, даже если Google рекомендует это. Это похоже на возврат значения из функции через глобальную переменную. Я достигаю ожидаемого потока, используя newFragment.setTargetFragment(this) при переходе вперед к детализированному/редактированному фрагменту, а затем вызывая getTargetFragment().onActivityResult(result, getTargetRequestCode()) перед переходом назад (например, fragmentManger.popBackstack()). Одним из преимуществ является то, что Android автоматически обрабатывает случаи, когда одного из фрагментов нет. Однако я не нашел способа добиться такого потока с помощью компонента навигации. - person jskierbi; 31.03.2019
comment
@jskierbi - конечно, у этой проблемы есть серьезные проблемы - ваш целевой фрагмент не запущен, и поэтому выполнять там какие-либо операции небезопасно, вы тесно связали два своих фрагмента вместе, и вы вынуждены использовать Intent (и объекты Parcelable), когда на самом деле подойдет любой объект. Конечно, это не идеально, поэтому я бы рекомендовал пометить существующий запрос функции звездочкой для родной navigateForResult() тип API. - person ianhanniballake; 31.03.2019
comment
@ianhanniballake, что происходит с этой общей моделью просмотра, когда оба фрагмента совместного использования уничтожаются? Будет ли он GCed или просто останется там, пока активность не будет в памяти? - person rd7773; 22.01.2020
comment
если вы используете getActivity() или requireActivity(), все данные будут жить до тех пор, пока ваша активность не завершится, это означает, что всплывающие фрагменты сохранят данные в модели общего представления (если вы не обновите их в onDestroy() всплывающего фрагмента), поскольку instance находится в жизненном цикле Activity, а не в жизненном цикле Fragment @rd7773 - person Gastón Saillén; 21.03.2020
comment
Общая модель просмотра действительно работает, и это законное использование модели представления среди фрагментов (и рекомендуется), но это не лучшее решение для DialogFragments из-за их временного характера и временного характера данных, которые они обрабатывают. - person Brill Pappin; 16.06.2020
comment
Вам не нужно использовать общую ViewModel. Вполне возможно установить targetFragment с помощью компонента навигации: см. stackoverflow.com/a/64995726/6007104 - person Luke; 25.11.2020

Чтобы уточнить принятый ответ:

(1) Создайте модель общего представления, которая будет использоваться для обмена данными между фрагментами в этом действии.

public class SharedViewModel extends ViewModel {

    private final MutableLiveData<Double> aDouble = new MutableLiveData<>();

    public void setDouble(Double aDouble) {
        this.aDouble.setValue(aDouble);
    }

    public LiveData<Double> getDouble() {
        return aDouble;
    }
}

(2) Сохраните данные, к которым вы хотите получить доступ, в модели представления. Обратите внимание на область действия модели представления (getActivity).

SharedViewModel svm =ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
svm.setDouble(someDouble);

(3) Пусть фрагмент реализует интерфейс обратного вызова диалога и загружает диалог без установки целевого фрагмента.

fragment.setOnDialogSubmitListener(this);
fragment.show(getActivity().getSupportFragmentManager(), TAG);

(4) В диалоговом окне извлеките данные.

SharedViewModel svm =ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
svm.getDouble().observe(this, new Observer<Double>() {
    @Override
    public void onChanged(Double aDouble) {
        // do what ever with aDouble
    }
}); 
person Jeffrey    schedule 18.03.2019

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

Импорт

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
 implementation 'androidx.navigation:navigation-fragment-ktx:2.2.1'

ParentFragment (хост SharedViewModel)

class ParentFragment:Fragment() {
 private val model: SharedViewModel by viewModels()
}

Детский фрагмент

class ChildFragment:Fragment(){
private val model: SharedViewModel by viewModels ({requireParentFragment()})
}

Таким образом, при этом будет размещена модель sharedviewmodel в родительском фрагменте, а дочерний фрагмент, зависящий от этого родительского фрагмента, будет иметь доступ к тому же экземпляру SharedViewModel, и когда вы вытолкнете (то есть уничтожите фрагмент), ваш метод onCleared() сработает на вашем viewmodel и эта shareviewmodel будут очищены, а также все их данные.

Таким образом, у вас нет MainActivity для хранения всех данных, которыми делятся фрагменты, и вам не нужно очищать эти данные каждый раз, когда вы оставляете фрагмент, который использует SharedViewModel

Теперь в альфа-версии вы можете передавать данные между навигациями, используя также модель представления, которая будет сохранять данные между навигациями, скажем, вы хотите обмениваться данными между фрагментом B и фрагментом A, теперь вы можете сделать это просто с помощью двух строк

https://developer.android.com/guide/navigation/navigation-programmatic#returning_a_result

person Gastón Saillén    schedule 21.03.2020
comment
Намного лучший ответ, чем принятый. SharedViewModel, связанный с активностью, определенно не подходит, если вы хотите один раз обмениваться данными между двумя фрагментами. - person reVerse; 24.04.2020

Ни один из существующих ответов на самом деле не отвечает на ваш вопрос — как мы можем установить целевой фрагмент диалога при использовании компонентов навигации?

Оказывается, нам не нужно использовать (откровенно говоря, ужасный) шаблон общей ViewModel. На самом деле довольно легко установить целевой фрагмент с помощью компонента навигации, если вы знаете, как это сделать.

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

https://lukeneedham.medium.com/using-targetfragment-with-jetpack-navigation-component-9c4302e8c062

Вы также можете просмотреть Gist здесь:

https://gist.github.com/LukeNeedham/83f0bdaa8d56d03d11f727967eb327f2

Ключ является пользовательским FragmentFactory:

fun FragmentManager.autoTarget() {
    fragmentFactory = ChildManagerFragmentFactory(this)
}

class ChildManagerFragmentFactory(
    private val fragmentManager: FragmentManager
) : AutoTargetFragmentFactory() {
    override fun getCurrentFragment() =
        fragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.firstOrNull()
}

abstract class AutoTargetFragmentFactory : FragmentFactory() {
    abstract fun getCurrentFragment(): Fragment?

    override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
        val fragment = super.instantiate(classLoader, className)
        val currentFragment = getCurrentFragment()
        fragment.setTargetFragment(currentFragment, REQUEST_CODE)
        return fragment
    }

    companion object {
        const val REQUEST_CODE = 0
    }
}

А затем просто используйте так:

class MainActivity : AppCompatActivity(R.layout.activity_main) {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportFragmentManager.autoTarget()
    }
}
person Luke    schedule 24.11.2020
comment
Обратите внимание, что этот подход в корне ошибочен из-за пакетного характера обновлений FragmentManager. Например, действие с popUpTo приведет к тому, что ваш getCurrentFragment() вернет собирается быть извлеченным фрагментом, что приводит к тому, что ваш новый фрагмент содержит жесткую ссылку на фрагмент, который будет фактически уничтожен до того, как новый фрагмент пройдет какие-либо переходы жизненного цикла, мгновенно создавая утечку памяти. - person ianhanniballake; 25.11.2020
comment
Также имейте в виду, что setTargetFragment() сам по себе является устарело, с заменами на обоих уровня навигации и уровень фрагмента, который, в отличие от setTargetFragment(), будет работать, когда используя будущие функции, такие как несколько бэкстеков и единый жизненный цикл. - person ianhanniballake; 25.11.2020
comment
Привет Ян, просто хочу сказать, что я большой фанат! Спасибо, что указали на эту утечку, которую я проглядел. Должен сказать, я чувствую, что эти замены являются чем-то вроде шага назад: я использую компонент навигации Jetpack, поэтому мне больше не нужно беспокоиться о связках и ключах для передачи аргументов, но, по-видимому, мне нужно беспокоиться о них при возврате результатов. . Огромным преимуществом targetFragment является то, что он позволил нам реализовать интерфейсы, которые можно было бы использовать для передачи результатов типобезопасным способом, в отличие от замен на основе Bundle. - person Luke; 25.11.2020
comment
Есть ли с вашей стороны какой-либо план по реализации безопасного в стиле аргументов, безопасного для типов обертки для передачи результатов назад, точно так же, как мы можем в настоящее время передавать аргументы вперед @ianhanniballake ? - person Luke; 26.11.2020