Получите результат от DialogFragment

232

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

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

В настоящее время я выполняю вызывающее действие DismissListenerи даю DialogFragment ссылку на это действие. Затем Dialog вызывает OnDimissметод в действии, и действие получает результат из объекта DialogFragment. Очень грязно и не работает при изменении конфигурации (изменении ориентации), так как DialogFragment теряет ссылку на действие.

Спасибо за любую помощь.

Джеймс Кросс
источник
10
DialogFragments - это всего лишь фрагменты. Ваш подход на самом деле является рекомендуемым способом использования фрагментов для обратной связи с основной деятельностью. developer.android.com/guide/topics/fundamentals/…
codinguser
1
Спасибо за это. Я был очень близок (как вы сказали). Помочь мне в этом связанном документе было использование onAttach () и приведение действия к слушателю.
Джеймс Кросс
2
@codinguser, @Styx - «указание на DialogFragment ссылку на действие» - эта деталь немного рискованна, так как можно воссоздать и то, Activityи другое DialogFragment. Использование Activityпереданного onAttach(Activity activity)является правильным и рекомендуемым способом.
Сстн

Ответы:

246

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

Это похоже на злоупотребление onActivityResult(), тем более что оно вообще не связано с деятельностью. Но я видел это рекомендованным официальными людьми Google, и, возможно, даже в демоверсиях API. Я думаю, что это то, что g/setTargetFragment()было добавлено.

Timmmm
источник
2
setTargetFragment упоминает, что код запроса предназначен для использования в onActivityResult, поэтому я думаю, что этот подход можно использовать.
Георгий
87
Что, если целью является деятельность?
Фернандо Гальего
9
Если цель - это активность, я бы объявил интерфейс с методом типа «void onActivityResult2 (int requestCode, int resultCode, Intent data)» и реализовал его с помощью Activity. В DialogFragment просто getActivity и проверьте этот интерфейс и вызовите его соответствующим образом.
Руслан Янчишин
4
Это не хорошее решение. Он не будет работать после сохранения и восстановления состояния фрагмента диалога. LocalBroadcastManager является лучшим решением в этом случае.
Ник
4
@ Ник Это просто неправда. Это лучшее решение. Нет проблем при сохранении и восстановлении состояния. Если у вас когда-либо возникали проблемы, вы использовали неправильный менеджер фрагментов. Целевой фрагмент / вызывающий объект должен использовать getChildFragmentManager () для отображения диалога.
Невероятное
140

Как вы можете видеть здесь, есть очень простой способ сделать это.

В ваш DialogFragmentдобавить интерфейс слушателя, как:

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

Затем добавьте ссылку на этого слушателя:

private EditNameDialogListener listener;

Это будет использоваться для «активации» метода (ов) слушателя, а также для проверки того, реализует ли родительский Activity / Fragment этот интерфейс (см. Ниже).

В 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', но если вызывающий абонент Fragment?
Брейс Габин
1
Я не уверен, что полностью вас понимаю. Но это будет работать так же, если вызывающий абонент Fragment.
Ассаф Гамлиэль
2
Если вызывающий объект был Fragmentтогда, вы можете сделать несколько вещей: 1. Передать фрагмент как ссылку (может быть, это не очень хорошая идея, поскольку вы можете вызвать утечки памяти). 2. Используйте FragmentManagerи позвоните findFragmentByIdили findFragmentByTagон получит фрагменты, которые существуют в вашей деятельности. Надеюсь, это помогло. Хорошего дня!
Ассаф Гамлиэль
5
Проблема с этим подходом заключается в том, что фрагмент не очень хорошо сохраняет объект, так как он предназначен для воссоздания, например, попытайтесь изменить ориентацию, ОС восстановит фрагмент, но экземпляр слушателя больше не будет доступен
Necronet
5
@LOG_TAG посмотрите на ответ @ Timmmm. setTargetFragment()и getTargetFragment()волшебство.
Брейс Габин
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, который вы назвали, я покажу, как это работает через секунду. ResultCode - это код, который вы отправляете обратно из DialogFragment, сообщая вашей текущей ожидающей активности, фрагменту или фрагменту активности, что произошло.

Следующий фрагмент кода - это вызов 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, устанавливая requestCode (который будет вызывать onActivityResult (...) после закрытия Dialog, и затем вы показываете диалоговое окно. Это так просто.

Теперь в вашем DialogFragment вам нужно просто добавить одну строку непосредственно перед dismiss()тем, чтобы вы отправили resultCode обратно в onActivityResult ().

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

Вот и все. Обратите внимание, resultCode определяется как то, int resultCodeчто я установил resultCode = 1;в этом случае.

Вот и все, теперь вы можете отправить результат вашего DialogFragment обратно в ваш вызывающий Activity, Fragment или FragmentActivity.

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

РЕДАКТИРОВАТЬ 06.24.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.

Brandon
источник
4
Этот подход не будет работать, если target является Activity, потому что вы не можете вызвать его onActivityResult (из вашего DialogFragment) из-за уровня защищенного доступа.
Руслан Янчишин
2
Это просто не соответствует действительности. Я использую этот точный код в своих проектах. Вот откуда я его вытащил и он работает просто отлично. Помните, что если у вас есть проблема с уровнем защищенного доступа, вы можете изменить уровень доступа для любого метода и класса с защищенного на частный или общедоступный, если это необходимо.
Брэндон
6
Здравствуйте, вы говорите, что можете вызывать dialogFrag.setTargetFragment(this, 1)из Activity, но этот метод получает Fragment в качестве первого аргумента, так что это не может быть приведено. Я прав ?
MondKin
1
Я опубликую некоторые ответы для вас всех в нескольких, чтобы объяснить вещи деятельности.
Брэндон
1
@Swift @lcompare, возможно, вам нужно переопределить onAttach (контекстный контекст) в вашем DialogFragment. @Override public void onAttach(Context context) { super.onAttach(context); yourInterface = (YourInterface) context; }
Вот
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в условии if.

 @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) Переопределить OnFragmentInteraction в действии

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

Больше информации об этом: https://developer.android.com/training/basics/fragments/communicating.html

lcompare
источник
Спасибо, что подвели итог так хорошо. Учебник по Android для разработчиков - всего лишь одна заметка для других - предлагает переопределить public void onAttachфрагмент и выполнить приведение к нему активности
Big_Chair
8

Один простой способ, который я нашел, заключался в следующем: реализовать это ваш dialogFragment,

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

Тост должен показать, что он работает. Работал на меня.

ZeWolfe15
источник
Я не уверен, если это правильный способ сделать это, но это, безусловно, работает :)
Soshial
Лучше использовать Interface, чем жестко связывать с конкретными классами.
waqaslam
6

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

Адиль Хуссейн
источник
2
Мне нравится это решение, считается ли это хорошей или лучшей практикой в ​​Android?
Бенджамин Шарбау
1
Мне очень понравился урок, спасибо за публикацию. Я хочу добавить, что в зависимости от того, что вы пытаетесь достичь, один из методов может быть более полезным, чем другой. Я бы предложил местный маршрут широковещания, если у вас есть несколько входов / результатов, отправляемых обратно в активность из диалогового окна. Я бы порекомендовал использовать маршрут onActivityResult, если ваш вывод очень простой / простой. Таким образом, чтобы ответить на вопрос о наилучшей практике, это зависит от того, чего вы пытаетесь достичь!
Брэндон
1
@AdilHussain Ты прав. Я сделал предположение, что люди используют фрагменты в своей деятельности. Параметр setTargetFragment отлично подходит, если вы общаетесь с Fragment и DialogFragment. Но вам нужно использовать метод Broadcast, когда это Activity, вызывающая DialogFragment.
Брэндон
3
Ради любви Фу, не используйте трансляции! Это открывает ваше приложение для понимания проблем безопасности. Также я нахожу, что худшее приложение для Android, с которым мне приходится работать в эфире. Можете ли вы придумать лучший способ сделать код полностью непригодным для использования? Теперь мне приходится отключать широковещательные приемники вместо строки кода CLEAR? Чтобы было ясно, есть использование для вещания, но не в этом контексте! НИКОГДА в этом контексте! Это просто небрежно. Местный или нет. Обратные звонки - это все, что вам нужно.
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

Malachiasz
источник
2

В моем случае мне нужно было передать аргументы в targetFragment. Но я получил исключение «Фрагмент уже активен». Поэтому я объявил интерфейс в моем DialogFragment, который реализовал parentFragment. Когда parentFragment запускает DialogFragment, он устанавливает себя как TargetFragment. Затем в DialogFragment я позвонил

 ((Interface)getTargetFragment()).onSomething(selectedListPosition);
Lanitka
источник
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");
}
Yessy
источник
-3

Просто чтобы он был одним из вариантов (так как никто еще не упомянул об этом) - вы можете использовать шину событий, такую ​​как Otto. Итак, в диалоге вы делаете:

bus.post(new AnswerAvailableEvent(42));

И пусть ваш абонент (активность или фрагмент) подписаться на него:

@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
   // TODO: React to the event somehow!
}
Деннис К
источник