Есть ли метод, который работает как стартовый фрагмент для результата?

95

У меня сейчас есть фрагмент в оверлее. Это для входа в сервис. В приложении для телефона каждый шаг, который я хочу показать в оверлее, - это отдельные экраны и действия. Процесс входа состоит из 3 частей, каждая из которых имеет собственное действие, которое вызывается с помощью startActivityForResult ().

Теперь я хочу сделать то же самое, используя фрагменты и наложение. В оверлее будет показан фрагмент, соответствующий каждому действию. Проблема в том, что эти фрагменты размещены в действии в Honeycomb API. Я могу заставить работать первый фрагмент, но затем мне нужно startActivityForResult (), что невозможно. Есть ли что-то в строке startFragmentForResult (), где я могу запустить новый фрагмент, и когда это будет сделано, вернуть результат предыдущему фрагменту?

CACuzcatlan
источник

Ответы:

58

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

Просто помните, что у вас всегда есть связь между фрагментом и его действием. Начало и завершение результата - это механизм взаимодействия между Действиями - Действия могут затем делегировать любую необходимую информацию своим Фрагментам.

ЛеффельМания
источник
11
Кроме того, при загрузке одного фрагмента из другого вы можете установить целевой фрагмент и onActivityResultпри желании обратиться к методу родительского фрагмента .
PJL
4
Ваш ответ неполный, фрагменты используют жизненный цикл, как действия. Итак, мы не можем передавать аргументы через методы, но мы должны использовать пакеты для передачи значений. Даже если фрагмент где-то установил значение, нам нужно знать, когда он закончился. Должны ли предыдущие фрагменты получить значение при запуске / возобновлении? Это идея. Но нет правильного способа сохранить значение, фрагмент может быть вызван несколькими другими фрагментами / действиями.
Loenix 08
1
ну, было бы разумно вызвать «начальный фрагмент для результата», поскольку это дало бы гораздо больше гибкости -> почему фрагмент, как дочерний элемент, должен знать о своем родителе? Было бы не намного чище, если бы фрагмент просто выполнял свою работу и возвращал результат независимо от того, в какой деятельности он живет. Особенно в контексте приложений с одним действием.
daneejela
А как насчет потенциальных утечек памяти?
daneejela
60

Если хотите, есть несколько способов связи между фрагментами,

setTargetFragment(Fragment fragment, int requestCode)
getTargetFragment()
getTargetRequestCode()

Вы можете выполнить обратный вызов, используя их

Fragment invoker = getTargetFragment();
if(invoker != null) {
    invoker.callPublicMethod();
}
нагоя0
источник
Я не подтвердил, но, возможно, это работает. Итак, вам следует позаботиться об утечках памяти, вызванных циклической ссылкой в ​​результате злоупотребления setTargetFragment().
nagoya0
3
Лучшее решение на данный момент! Отлично работает, когда есть одно действие и стопка фрагментов, когда попытка найти нужный фрагмент для уведомления была бы кошмаром. Спасибо
Дэмиен
1
@ userSeven7s: это не сработает для случая замены, но должно работать для скрытого кейса
Мухаммад Бабар
1
@ nagoya0 сделает это лучше, если мы используем WeakReference для целевого фрагмента
quangson91
Хотя это работает, setTargetFragmentв настоящее время не рекомендуется. Смотрите setResultListenerв stackoverflow.com/a/61881149/2914140 здесь.
CoolMind
11

Мы можем просто использовать одну и ту же ViewModel между фрагментами

SharedViewModel

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel

class SharedViewModel : ViewModel() {

    val stringData: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

}

FirstFragment

import android.arch.lifecycle.Observer
import android.os.Bundle
import android.arch.lifecycle.ViewModelProviders
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

class FirstFragment : Fragment() {

    private lateinit var sharedViewModel: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activity?.run {
            sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        sharedViewModel.stringData.observe(this, Observer { dateString ->
            // get the changed String
        })

    }

}

ВторойФрагмент

import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGrou

class SecondFragment : Fragment() {

    private lateinit var sharedViewModel: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activity?.run {
            sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java)
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        changeString()
    }

    private fun changeString() {
        sharedViewModel.stringData.value = "Test"
    }

}
Левон Петросян
источник
3
Привет, Левон, я думаю, что приведенный выше код не будет использовать один и тот же экземпляр модели представления. Вы передаете этот (фрагмент) для ViewModelProviders.of (this) .get (SharedViewModel :: class.java). Это создаст два отдельных экземпляра для фрагментов. Вам необходимо передать activity ViewModelProviders.of (activity) .get (SharedViewModel :: class.java)
Шайлендра Патил
@ShailendraPatil Хорошая уловка, сейчас исправлю.
Левон Петросян
8

Недавно Google только что добавил новую возможность, FragmentManagerблагодаря которой FragmentManagerон может выступать в качестве центрального хранилища результатов фрагментов. Мы можем легко передавать данные между фрагментами.

Стартовый фрагмент.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Use the Kotlin extension in the fragment-ktx artifact
    setResultListener("requestKey") { key, bundle ->
        // We use a String here, but any type that can be put in a Bundle is supported
        val result = bundle.getString("bundleKey")
        // Do something with the result...
    }
}

Фрагмент, результат которого мы хотим вернуть.

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact
    setResult("requestKey", bundleOf("bundleKey" to result))
}

Фрагмент взят из официальных документов Google. https://developer.android.com/training/basics/fragments/pass-data-between#kotlin

На момент написания этого ответа эта функция все еще находится в alphaсостоянии. Вы можете попробовать это, используя эту зависимость.

androidx.fragment:fragment:1.3.0-alpha05
Бунья Китпитак
источник
4

Мои 2 цента.

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

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

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

@Override
public void onHiddenChanged(boolean hidden) {
    super.onHiddenChanged(hidden);
    if (hidden) return;
    Result result = Result.getAndReset();
    if (result == Result.Refresh) {
        refresh();
    }
}

public enum Result {
    Refresh;

    private static Result RESULT;

    public static void set(Result result) {
        if (RESULT == Refresh) {
            // Refresh already requested - no point in setting anything else;
            return;
        }
        RESULT = result;
    }

    public static Result getAndReset() {
        Result result = RESULT;
        RESULT = null;
        return result;
    }
}
Алик Эльзин-килака
источник
Что такое getAndReset()метод?
EpicPandaForce
Также не onResume() вызывается и первый фрагмент при закрытии второго?
PJ_Finnegan
2

В вашем фрагменте вы можете вызвать getActivity (). Это даст вам доступ к действию, создавшему фрагмент. Оттуда вы можете вызвать свой метод настройки, чтобы установить значения или передать значения.

Summved Jain
источник
1

Есть Android-библиотека - FlowR которая позволяет запускать фрагменты для результатов.

Запускаем фрагмент для результата.

Flowr.open(RequestFragment.class)
    .displayFragmentForResults(getFragmentId(), REQUEST_CODE);

Обработка результатов в вызывающем фрагменте.

@Override
protected void onFragmentResults(int requestCode, int resultCode, Bundle data) {
    super.onFragmentResults(requestCode, resultCode, data);

    if (requestCode == REQUEST_CODE) {
        if (resultCode == Activity.RESULT_OK) {
            demoTextView.setText("Result OK");
        } else {
            demoTextView.setText("Result CANCELED");
        }
    }
}

Установка результата во Фрагменте.

Flowr.closeWithResults(getResultsResponse(resultCode, resultData));
Рагунатх Джавахар
источник
1

Решение с использованием интерфейсов (и Kotlin). Основная идея состоит в том, чтобы определить интерфейс обратного вызова, реализовать его в своей деятельности, а затем вызвать его из своего фрагмента.

Сначала создайте интерфейс ActionHandler:

interface ActionHandler {
    fun handleAction(actionCode: String, result: Int)
}

Затем вызовите это от вашего ребенка (в данном случае вашего фрагмента):

companion object {
    const val FRAGMENT_A_CLOSED = "com.example.fragment_a_closed"
}

fun closeFragment() {
    try {
        (activity as ActionHandler).handleAction(FRAGMENT_A_CLOSED, 1234)
    } catch (e: ClassCastException) {
        Timber.e("Calling activity can't get callback!")
    }
    dismiss()
}

Наконец, реализуйте это в своем родителе, чтобы получить обратный вызов (в данном случае - Activity):

class MainActivity: ActionHandler { 
    override fun handleAction(actionCode: String, result: Int) {
        when {
            actionCode == FragmentA.FRAGMENT_A_CLOSED -> {
                doSomething(result)
            }
            actionCode == FragmentB.FRAGMENT_B_CLOSED -> {
                doSomethingElse(result)
            }
            actionCode == FragmentC.FRAGMENT_C_CLOSED -> {
                doAnotherThing(result)
            }
        }
    }
Джейк Ли
источник
0

Самый простой способ вернуть данные - использовать setArgument (). Например, у вас есть fragment1, который вызывает fragment2, который вызывает fragment3, fragment1 -> framgnet2 -> fargment3

Во фрагменте1

public void navigateToFragment2() {
    if (fragmentManager == null) return;

    Fragment2 fragment = Fragment2.newInstance();
    String tag = "Fragment 2 here";
    fragmentManager.beginTransaction()
            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
            .add(R.id.flContent, fragment, tag)
            .addToBackStack(null)
            .commitAllowingStateLoss();
}

Во фрагменте 2 мы называем фрагмент 3 как обычно

private void navigateToFragment3() {
    if (fragmentManager == null) return;
    Fragment3 fragment = new Fragment3();
    fragmentManager.beginTransaction()
            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
            .replace(R.id.flContent, fragment, tag)
            .addToBackStack(null)
            .commit();
}

Когда мы закончили нашу задачу во фрагменте 3, теперь мы вызываем это так:

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
if (fragmentManager == null) return;
fragmentManager.popBackStack();
Bundle bundle = new Bundle();
bundle.putString("bundle_filter", "data");
fragmentManager.findFragmentByTag("Fragment 2 here").setArguments(bundle);

Теперь во фрагменте 2 мы можем легко вызвать аргументы

@Override
public void onResume() {
    super.onResume();
    Bundle rgs = getArguments();
    if (args != null) 
        String data = rgs.getString("bundle_filter");
}
Kirk_hehe
источник
Будьте осторожны с этим, в некоторых случаях он может работать, но если ваш фрагмент имеет исходные аргументы (в данном примере «фрагмент 2»), это переопределит исходные аргументы, использованные для создания фрагмента, что может привести к несогласованному состоянию, если фрагмент уничтожен и воссоздан.
Хуан
0

Еще одна вещь, которую вы можете сделать в зависимости от вашей архитектуры, - это использовать общую ViewModel между фрагментами. Итак, в моем случае FragmentA - это форма, а FragmentB - это представление выбора элемента, в котором пользователь может искать и выбирать элемент, сохраняя его в ViewModel. Затем, когда я возвращаюсь к FragmentA, информация уже сохраняется!

Арджун
источник