Чи існує метод, який працює як стартовий фрагмент для результату?


95

На даний момент у мене є фрагмент у накладенні. Це для входу в службу. У телефонній програмі кожен крок, який я хочу показати в накладенні, - це власні екрани та дії. Існує 3 частини процесу входу в систему, кожна з яких мала власну активність, яку викликали за допомогою startActivityForResult ().

Тепер я хочу зробити те саме, використовуючи фрагменти та накладення. Накладене зображення покаже фрагмент, що відповідає кожному виду діяльності. Проблема полягає в тому, що ці фрагменти розміщуються під час дії в Honeycomb API. Я можу змусити перший фрагмент працювати, але тоді мені потрібно запуститиActivityForResult (), що неможливо. Чи є щось на кшталт startFragmentForResult (), де я можу почати новий фрагмент, і коли це буде зроблено, повернути результат до попереднього фрагмента?

Відповіді:


58

Всі Фрагменти живуть усередині діяльності. Починати Фрагмент для результату не має особливого сенсу, оскільки Діяльність, в якій він знаходиться, завжди має до нього доступ, і навпаки. Якщо Фрагменту потрібно передати результат, він може отримати доступ до своєї Діяльності та встановити свій результат і закінчити його. У разі обміну фрагментами в одній діяльності, активність все одно доступна для обох фрагментів, і всі ваші передані повідомлення можуть просто пройти через активність.

Тільки пам’ятайте, що у вас завжди є зв’язок між фрагментом та його діяльністю. Починаючи і закінчуючи результатом - це механізм комунікації між діяльністю. Діяльність може потім делегувати будь-яку необхідну інформацію своїм фрагментам.


11
Крім того, під час завантаження одного фрагмента з іншого ви можете встановити цільовий фрагмент і передзвонити onActivityResultметод батьківського фрагмента, якщо хочете.
PJL

4
Ваша відповідь неповна, фрагменти використовують життєвий цикл, як діяльність. Отже, ми не можемо просто передавати аргументи через методи, але нам слід використовувати пакети для передачі значень. Навіть якщо фрагмент десь встановлює значення, нам потрібно знати, коли він закінчився. Чи повинні попередні фрагменти отримувати значення, коли воно починається / відновлюється? Це ідея. Але немає належного способу зберігати значення, фрагмент може бути викликаний кількома іншими фрагментами / діями.
Loenix

1
ну, було б сенсом називати "стартовий фрагмент для результату", оскільки це дало б набагато більшу гнучкість -> навіщо фрагментувати, як дитина знає про своїх батьків? не було б набагато чистіше, щоб фрагмент просто виконував свою роботу і повертав результат, незалежно від того, в якій діяльності він живе. Особливо в контексті додатків для однієї активності.
daneejela

60

Якщо ви хочете, є кілька методів зв'язку між Фрагментами,

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

Ви можете здійснити зворотний дзвінок, використовуючи ці.

Fragment invoker = getTargetFragment();
if(invoker != null) {
    invoker.callPublicMethod();
}

Я не підтвердив, але, можливо, це працює. отже, ви повинні подбати про витоки пам'яті, спричинені циркулярними посиланнями через зловживання 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)
Patil

@ShailendraPatil Хороший улов, я зараз це виправлю.
Левон Петросян

6

Нещодавно 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 (). Це дасть вам доступ до діяльності, яка створила фрагмент. Звідти ви можете викликати свій метод налаштування, щоб встановити значення або передати значення.


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()
}

Нарешті, впровадьте це у своїх батьків, щоб отримати зворотний дзвінок (у цьому випадку ваша активність):

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);

Тепер у fragment2 ми можемо легко викликати аргументи

@Override
public void onResume() {
    super.onResume();
    Bundle rgs = getArguments();
    if (args != null) 
        String data = rgs.getString("bundle_filter");
}

Будьте обережні з цим, це може спрацювати в деяких випадках, але якщо у вашому фрагменті є оригінальні аргументи (у цьому прикладі - «фрагмент 2»), це замінить оригінальні аргументи, використані для створення фрагмента, що може призвести до непослідовного стану, якщо фрагмент руйнується і відтворюється.
Хуан

0

Інша справа, яку ви можете зробити залежно від вашої архітектури, - це використання спільного ViewModel між фрагментами. Отже, у моєму випадку FragmentA є формою, а FragmentB - поданням вибору елемента, де користувач може шукати та вибирати елемент, зберігаючи його у ViewModel. Потім, коли я повертаюся до FragmentA, інформація вже зберігається!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.