Отримайте результат від DialogFragment


232

Я використовую DialogFragments для низки речей: вибору елемента зі списку, введення тексту.

Який найкращий спосіб повернути значення (тобто рядок або елемент зі списку) назад до виклику діяльності / фрагменту?

В даний час я здійснюю активність виклику DismissListenerі реалізую DialogFragment посилання на цю діяльність. Потім діалог викликає OnDimissметод у діяльності, і активність захоплює результат від об'єкта DialogFragment. Дуже безладний, і він не працює на зміну конфігурації (зміна орієнтації), оскільки DialogFragment втрачає посилання на активність.

Дякуємо за будь-яку допомогу.


10
DialogFragments - це лише фрагменти. Ваш підхід насправді є рекомендованим способом використання фрагментів для зворотного зв'язку з основною діяльністю. developer.android.com/guide/topics/fundamentals/…
кодуванняuser

1
Дякую за це. Я був дуже близький (як ви сказали). Біт, який мені допоміг у зв’язаному документі, - це використання OnAttach () та передача активності слухачеві.
Джеймс Крос

2
@codinguser, @Styx - "даючи DialogFragment посилання на діяльність" - ця деталь є мало ризикованою, оскільки і те, Activityі DialogFragmentможливо можуть бути відтворені. Використання Activityпереданого onAttach(Activity activity)способу є правильним та рекомендованим способом.
sstn

Відповіді:


246

Використовуйте myDialogFragment.setTargetFragment(this, MY_REQUEST_CODE)з місця, де ви показуєте діалогове вікно, а потім, коли ваше діалогове вікно закінчиться, з нього ви можете зателефонувати getTargetFragment().onActivityResult(getTargetRequestCode(), ...)та реалізувати onActivityResult()у містить фрагмент.

Це здається зловживанням onActivityResult(), тим більше, що воно взагалі не передбачає діяльності. Але я бачив, як це рекомендують офіційні люди google, а може, навіть і в демонстраціях api. Я думаю, що це те, що g/setTargetFragment()було додано.


2
setTargetFragment згадує, що код запиту використовується для onActivityResult, тому я думаю, що добре використовувати цей підхід.
Джорджі

87
Що робити, якщо ціль - це діяльність?
Фернандо Ґаллего

9
Якщо цільовою є активність, я оголошу інтерфейс з таким методом, як "void onActivityResult2 (int requestCode, int resultCode, Intent data)" і реалізую його за допомогою діяльності. У DialogFragment просто getActivity і перевірте цей інтерфейс і зателефонуйте належним чином.
Руслан Янчишин

4
Це не гарне рішення. Він не працює після збереження та відновлення стану фрагмента діалогу. LocalBroadcastManager - найкраще рішення в цьому випадку.
Нік

4
@Nik Це просто неправда. Це найкраще рішення. При збереженні та відновленні держави проблем немає. Якщо у вас виникли проблеми, ви використовували неправильний менеджер фрагментів. Цільовий фрагмент / абонент повинен просто використовувати getChildFragmentManager () для відображення діалогового вікна.
Неймовірний січень

140

Як ви бачите тут, існує дуже простий спосіб зробити це.

У ваших DialogFragmentдодати інтерфейс слухача , як:

public interface EditNameDialogListener {
    void onFinishEditDialog(String inputText);
}

Потім додайте посилання на цього слухача:

private EditNameDialogListener listener;

Це буде використано для "активації" методу (ів) слухача, а також для перевірки, чи реалізована батьківська активність / фрагмент цього інтерфейсу (див. Нижче).

У Activity/ FragmentActivity/ Fragmentщо "закликав" DialogFragmentпросто реалізувати цей інтерфейс.

У DialogFragmentвсе, що вам потрібно додати в пункті, де ви хочете відмовитись DialogFragmentі повернути результат, це:

listener.onFinishEditDialog(mEditText.getText().toString());
this.dismiss();

Де mEditText.getText().toString()це те, що буде передано назад до дзвінка Activity.

Зауважте, що якщо ви хочете повернути щось інше, просто змініть аргументи, які висуває слухач.

Нарешті, слід перевірити, чи реально реалізовано інтерфейс батьківською діяльністю / фрагментом:

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    // Verify that the host activity implements the callback interface
    try {
        // Instantiate the EditNameDialogListener so we can send events to the host
        listener = (EditNameDialogListener) context;
    } catch (ClassCastException e) {
        // The activity doesn't implement the interface, throw exception
        throw new ClassCastException(context.toString()
                + " must implement EditNameDialogListener");
    }
}

Ця методика є дуже гнучкою та дозволяє передзвонити з результатом, навіть якщо ваш дон не бажає поки що виходити з діалогу.


13
Це чудово працює з Activity's і FragmentActivity' s, але якщо абонент a Fragment?
Brais Gabin

1
Я не впевнений, що вас цілком розумію. Але це буде працювати так само, якщо абонент є Fragment.
Ассаф Гамліель

2
Якщо абонент був Fragmentтоді, ви можете зробити кілька речей: 1. Передайте фрагмент як орієнтир (це може бути непоганою ідеєю, оскільки це може спричинити витік пам'яті). 2. Використовуйте FragmentManagerі виклик findFragmentByIdабо findFragmentByTagвін буде отримувати фрагменти , які існують у вашій діяльності. Сподіваюся, це допомогло. Гарного дня!
Assaf Gamliel

5
Проблема такого підходу полягає в тому, що фрагмент не дуже добре зберігає об'єкт, оскільки він призначений для відтворення, наприклад, спробуйте змінити орієнтацію, ОС відтворить фрагмент, але екземпляр слухача вже не буде доступний
Necronet

5
@LOG_TAG подивіться на відповідь @ Timmmm. setTargetFragment()і getTargetFragment()є магією.
Brais Gabin

48

Існує набагато простіший спосіб отримати результат від DialogFragment.

По-перше, у вашій діяльності, фрагменті чи фрагменті активності потрібно додати таку інформацію:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Stuff to do, dependent on requestCode and resultCode
    if(requestCode == 1) { // 1 is an arbitrary number, can be any int
         // This is the return result of your DialogFragment
         if(resultCode == 1) { // 1 is an arbitrary number, can be any int
              // Now do what you need to do after the dialog dismisses.
         }
     }
}

В requestCodeосновному, ваш int мітка для DialogFragment, який ви викликали, я покажу, як це працює за секунду. Код результатів - це код, який ви надсилаєте назад із DialogFragment, повідомляючи про свою поточну активність очікування, фрагмент або FragmentActivity про те, що сталося.

Наступний фрагмент коду, який слід запустити, - це виклик до DialogFragment. Приклад тут:

DialogFragment dialogFrag = new MyDialogFragment();
// This is the requestCode that you are sending.
dialogFrag.setTargetFragment(this, 1);     
// This is the tag, "dialog" being sent.
dialogFrag.show(getFragmentManager(), "dialog");

За допомогою цих трьох рядків ви декларуєте свій DialogFragment, встановлюючи codeCode (який буде викликати onActivityResult (...), коли діалог буде відхилений, і ви покажете діалогове вікно. Це все просто.

Тепер у діалоговому фрагменті вам потрібно просто додати один рядок безпосередньо перед dismiss()тим, щоб ви повернули результат коду до onActivityResult ().

getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, getActivity().getIntent());
dismiss();

Це воно. Зауважте, код коду результату визначається таким, int resultCodeякий я встановив resultCode = 1;у цьому випадку.

Ось так, тепер ви можете відправити результат свого DialogFragment назад у свою діяльність виклику, фрагмент або FragmentActivity.

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

РЕДАКЦІЯ 24.06.2016 Прошу вибачення за помилковий код вище. Але ви, безумовно, не можете отримати результат назад до діяльності, розглядаючи як рядок:

dialogFrag.setTargetFragment(this, 1);

встановлює ціль, Fragmentа не Activity. Отже, для цього вам потрібно скористатися втіленням InterfaceCommunicator.

У вашому DialogFragmentнаборі глобальна змінна

public InterfaceCommunicator interfaceCommunicator;

Створіть загальнодоступну функцію для її обробки

public interface InterfaceCommunicator {
    void sendRequestCode(int code);
}

Потім , коли ви будете готові відправити код назад до Activityколи DialogFragmentвиконуються біг, просто додайте рядок перед вами dismiss();вашим DialogFragment:

interfaceCommunicator.sendRequestCode(1); // the parameter is any int code you choose.

У вашій діяльності зараз вам потрібно зробити дві речі, перше - видалити той рядок коду, який більше не застосовується:

dialogFrag.setTargetFragment(this, 1);  

Потім реалізуйте інтерфейс і все закінчено. Ви можете зробити це, додавши наступний рядок до implementsпункту в самому верху вашого класу:

public class MyClass Activity implements MyDialogFragment.InterfaceCommunicator

А потім @Overrideфункція в діяльності,

@Override
public void sendRequestCode(int code) {
    // your code here
}

Ви використовуєте цей метод інтерфейсу так само, як і onActivityResult()метод. За винятком того, що метод інтерфейсу призначений для, DialogFragmentsа інший - для Fragments.


4
Цей підхід не буде працювати, якщо ціль - "Діяльність", оскільки ви не можете викликати його onActivityResult (з діалогового фрагмента) через захищений рівень доступу.
Руслан Янчишин

2
Це просто неправда. Я використовую цей точний код у своїх проектах. Ось звідки я його витягнув, і він працює просто чудово. Будь ласка, пам’ятайте, якщо у вас виникає ця проблема із захищеним рівнем доступу, ви можете змінити рівень свого доступу для будь-якого методу та класу із захищеного на приватний або загальнодоступний, якщо це необхідно.
Брендон

6
Привіт, Ви кажете, що Ви можете зателефонувати dialogFrag.setTargetFragment(this, 1)з Діяльності, але цей метод отримує Фрагмент як перший аргумент, тому це не вдалося проголосувати. Маю рацію ?
MondKin

1
Я опублікую декілька відповідей для вас усіх у кількох, щоб пояснити інформацію про діяльність.
Брендон

1
@Swift @lcompare вам, мабуть, потрібно перекрити onAttach (контекст контексту) у вашому DialogFragment. Як і так:@Override public void onAttach(Context context) { super.onAttach(context); yourInterface = (YourInterface) context; }
lidkxx

20

Ну, це занадто пізно, можливо, відповісти, але ось що я зробив, щоб отримати результати від DialogFragment. дуже схожа на відповідь @ brandon. Ось я дзвоню DialogFragmentз фрагмента, просто помістіть цей код, куди ви дзвоните у свій діалог.

FragmentManager fragmentManager = getFragmentManager();
            categoryDialog.setTargetFragment(this,1);
            categoryDialog.show(fragmentManager, "dialog");

де categoryDialogмій, DialogFragmentякий я хочу зателефонувати, і після цього у вашій реалізації dialogfragmentрозмістіть цей код там, де ви налаштовуєте свої дані з наміром. Значення resultCodeдорівнює 1, ви можете встановити його або використовувати визначене системою.

            Intent intent = new Intent();
            intent.putExtra("listdata", stringData);
            getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, intent);
            getDialog().dismiss();

тепер настав час повернутися до виклику фрагменту та реалізувати цей метод. перевірити валідність даних або успіх результату, якщо ви хочете, resultCodeі requestCodeякщо це потрібно.

 @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);        
        //do what ever you want here, and get the result from intent like below
        String myData = data.getStringExtra("listdata");
Toast.makeText(getActivity(),data.getStringExtra("listdata"),Toast.LENGTH_SHORT).show();
    }

10

Інший підхід, щоб дозволити фрагменту повідомляти про свою діяльність :

1) Визначте у фрагменті загальнодоступний інтерфейс і створіть для нього змінну

public OnFragmentInteractionListener mCallback;

public interface OnFragmentInteractionListener {
    void onFragmentInteraction(int id);
}

2) Передайте активність змінній mCallback у фрагменті

try {
    mCallback = (OnFragmentInteractionListener) getActivity();
} catch (Exception e) {
    Log.d(TAG, e.getMessage());
}

3) Втілити слухача у свою діяльність

public class MainActivity extends AppCompatActivity implements DFragment.OnFragmentInteractionListener  {
     //your code here
}

4) Перекрийте взаємодію OnFragmentIn в діяльності

@Override
public void onFragmentInteraction(int id) {
    Log.d(TAG, "received from fragment: " + id);
}

Більше інформації про це: https://developer.android.com/training/basics/fragments/communicating.html


Дякуємо, що так добре підсумували його. Підручник для Android Devs - лише одна примітка для інших - пропонує перекрити public void onAttachфрагмент і виконати активну трансляцію
Big_Chair

8

Я знайшов один із простих способів: реалізуйте це ваш діалоговий фрагмент,

  CallingActivity callingActivity = (CallingActivity) getActivity();
  callingActivity.onUserSelectValue("insert selected value here");
  dismiss();

А потім у діяльності, яка викликала фрагмент діалогу, створіть відповідну функцію як таку:

 public void onUserSelectValue(String selectedValue) {

        // TODO add your implementation.
      Toast.makeText(getBaseContext(), ""+ selectedValue, Toast.LENGTH_LONG).show();
    }

Тост - це показати, що він працює. Працювали для мене.


Я не впевнений, чи правильно це зробити, але це, безумовно, працює :)
soshial

Краще використовувати Interface, ніж важко з’єднувати з класами бетону.
waqaslam

6

Я дуже здивований бачити , що ніхто не запропонував використовувати місцеве мовлення для DialogFragmentдля Activityспілкування! Я вважаю, що це набагато простіше і чіткіше, ніж інші пропозиції. По суті, ви реєструєтесь для того, Activityщоб слухати трансляції, і ви надсилали локальні трансляції зі своїх DialogFragmentпримірників. Простий. Покрокове керівництво про те, як все це налаштувати, дивіться тут .


2
Мені подобається це рішення, чи вважається це гарною чи найкращою практикою в Android?
Бенджамін Шарбау

1
Мені дуже сподобався підручник, дякую за публікацію. Я хочу додати, що залежно від того, що ви намагаєтеся здійснити, будь-який метод може бути кориснішим, ніж інший. Я б запропонував місцевий маршрут трансляції, якщо у вас буде відкрито декілька входів / результатів у діалоговому вікні. Я рекомендую використовувати маршрут onActivityResult, якщо ваш вихід дуже базовий / простий. Отже, щоб відповісти на питання найкращої практики, це залежить від того, що ви намагаєтеся виконати!
Брендон

1
@AdilHussain Ти маєш рацію. Я зробив припущення, що люди використовували фрагменти у межах своєї діяльності. Параметр setTargetFragment чудовий, якщо ви спілкуєтесь з фрагментом та діалоговим фрагментом. Але вам потрібно використовувати метод трансляції, коли це діяльність, яка викликає DialogFragment.
Брендон

3
Для любові до Foo, не використовуйте Трансляції !! Це відкриває вашу програму для вирішення проблем безпеки. Також я вважаю, що найгірший додаток для Android я маю працювати над трансляціями зловживань. Чи можете ви придумати кращий спосіб зробити код абсолютно непридатним? Тепер мені доведеться викорінювати приймачі широкомовної програми замість ЧИСЛОГО рядка коду? Щоб було зрозуміло, є широке використання, але не в цьому контексті! НІКОЛИ в цьому контексті! Це просто неохайно. Місцеві чи ні. Зворотні дзвінки - це все, що вам потрібно.
StarWind0

1
Крім Guava EventBus, ще одним варіантом є GreenRobot EventBus . Я не використовував Guava EventBus, але використовував GreenRobot EventBus і мав хороший досвід роботи з ним. Приємний і простий у використанні. Невеликий приклад того, як архітектуру програми для Android використовувати GreenRobot EventBus, дивіться тут .
Аділ Хуссей

3

Або поділіться ViewModel, як показано тут:

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}


public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // Update the UI.
        });
    }
}

https://developer.android.com/topic/libraries/architecture/viewmodel#sharing_data_between_fragments


2

У моєму випадку мені потрібно було передати аргументи до targetFragment. Але я отримав виняток "Фрагмент вже активний". Тому я оголосив інтерфейс у своєму діалоговому фрагменті, який реалізував parentFragment. Коли parentFragment запустив DialogFragment, він встановив себе як TargetFragment. Потім у DialogFragment я зателефонував

 ((Interface)getTargetFragment()).onSomething(selectedListPosition);

2

У Котліні

    // My DialogFragment
class FiltroDialogFragment : DialogFragment(), View.OnClickListener {
    
    var listener: InterfaceCommunicator? = null

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        listener = context as InterfaceCommunicator
    }

    interface InterfaceCommunicator {
        fun sendRequest(value: String)
    }   

    override fun onClick(v: View) {
        when (v.id) {
            R.id.buttonOk -> {    
        //You can change value             
                listener?.sendRequest('send data')
                dismiss()
            }
            
        }
    }
}

// Моя активність

class MyActivity: AppCompatActivity(),FiltroDialogFragment.InterfaceCommunicator {

    override fun sendRequest(value: String) {
    // :)
    Toast.makeText(this, value, Toast.LENGTH_LONG).show()
    }
}
 

Я сподіваюся, що він служить, якщо ви можете покращити, будь ласка, відредагуйте його. Моя англійська не дуже гарна


У моєму випадку діалогове вікно створюється з фрагмента не активності, тому це рішення не працює. Але мені подобається смайлик, який ви поставили :)
Ssenyonjo

0

якщо ви хочете відправити аргументи і отримати результат від другого фрагмента, ви можете використовувати Fragment.setArguments для виконання цього завдання

static class FirstFragment extends Fragment {
    final Handler mUIHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case 101: // receive the result from SecondFragment
                Object result = msg.obj;
                // do something according to the result
                break;
            }
        };
    };

    void onStartSecondFragments() {
        Message msg = Message.obtain(mUIHandler, 101, 102, 103, new Object()); // replace Object with a Parcelable if you want to across Save/Restore
                                                                               // instance
        putParcelable(new SecondFragment(), msg).show(getFragmentManager().beginTransaction(), null);
    }
}

static class SecondFragment extends DialogFragment {
    Message mMsg; // arguments from the caller/FirstFragment

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onViewCreated(view, savedInstanceState);
        mMsg = getParcelable(this);
    }

    void onClickOK() {
        mMsg.obj = new Object(); // send the result to the caller/FirstFragment
        mMsg.sendToTarget();
    }
}

static <T extends Fragment> T putParcelable(T f, Parcelable arg) {
    if (f.getArguments() == null) {
        f.setArguments(new Bundle());
    }
    f.getArguments().putParcelable("extra_args", arg);
    return f;
}
static <T extends Parcelable> T getParcelable(Fragment f) {
    return f.getArguments().getParcelable("extra_args");
}

-3

Просто, щоб мати його як один з варіантів (оскільки ніхто ще не згадував про це) - ви можете використовувати автобус подій, як Отто. Отже, у діалоговому вікні ви робите:

bus.post(new AnswerAvailableEvent(42));

І щоб ваш абонент (активність або фрагмент) підписався на нього:

@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
   // TODO: React to the event somehow!
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.