Получите результат от DialogFragment
Я использую DialogFragments для ряда вещей: выбор элемента из списка, ввод текста.
Каков наилучший способ вернуть значение (то есть строку или элемент из списка) обратно в вызывающую активность / фрагмент?
В настоящее время я выполняю звонки DismissListener
и предоставление DialogFragment ссылки на действие. Диалог тогда вызывает OnDimiss
метод в действии, и действие получает результат из объекта DialogFragment. Очень грязно и не работает при изменении конфигурации (изменении ориентации), так как DialogFragment теряет ссылку на действие.
Спасибо за любую помощь.
17 ответов
Использование myDialogFragment.setTargetFragment(this, MY_REQUEST_CODE)
из того места, где вы показываете диалог, а затем, когда ваш диалог закончен, из него вы можете позвонить getTargetFragment().onActivityResult(getTargetRequestCode(), ...)
и реализовать onActivityResult()
в содержащем фрагмент.
Это похоже на злоупотребление onActivityResult()
тем более, что это вообще не связано с деятельностью. Но я видел его рекомендованным официальными людьми Google, и, возможно, даже в демоверсиях API. Я думаю это то, что g/setTargetFragment()
были добавлены для.
Как вы можете видеть здесь, есть очень простой способ сделать это.
В вашем 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");
}
}
Этот метод очень гибкий и позволяет перезвонить с результатом, даже если вы еще не хотите закрывать диалог.
Существует гораздо более простой способ получить результат от 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 обратно в вызывающую активность, фрагмент или 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
,
Для тех, кто все еще читает это: setTargetFragment()
устарела. Теперь рекомендуется использоватьFragmentResultListener
API вроде этого:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setFragmentResultListener("requestKey") { key, bundle ->
val result = bundle.getString("resultKey")
// Do something with the result...
}
...
// Somewhere show your dialog
MyDialogFragment.newInstance().show(parentFragmentManager, "tag")
}
Тогда в вашем MyDialogFragment
установить результат:
button.setOnClickListener{
val result = "some string"
setFragmentResult("requestKey", bundleOf("resultKey" to result))
dismiss()
}
Ну, может быть слишком поздно, чтобы ответить, но вот что я сделал, чтобы получить результаты от 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();
}
Другой подход, позволяющий Фрагменту общаться до его Активности:
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
Один простой способ, который я нашел, заключался в следующем: реализовать это ваш 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();
}
Тост должен показать, что он работает. Работал на меня.
Я очень удивлен, увидев, что никто не предложил использовать местные трансляции для DialogFragment
в Activity
общение! Я считаю, что это намного проще и чище, чем другие предложения. По сути, вы регистрируетесь на Activity
прослушивать трансляции, и вы отправляете местные трансляции с вашего DialogFragment
экземпляров. Просто. Пошаговое руководство по настройке всего этого см. Здесь.
Или поделитесь 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
В котлине
// My DialogFragment
Класс 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()
}
}
}
}
// Моя активность
класс MyActivity: AppCompatActivity (), FiltroDialogFragment.InterfaceCommunicator {
override fun sendRequest(value: String) {
// :)
Toast.makeText(this, value, Toast.LENGTH_LONG).show()
}
}
Я надеюсь, что это работает, если вы можете улучшить, пожалуйста, отредактируйте его. Мой английский не очень хорош
В моем случае мне нужно было передать аргументы в targetFragment. Но я получил исключение "Фрагмент уже активен". Поэтому я объявил интерфейс в моем DialogFragment, который реализовал parentFragment. Когда parentFragment запускает DialogFragment, он устанавливает себя как TargetFragment. Затем в DialogFragment я позвонил
((Interface)getTargetFragment()).onSomething(selectedListPosition);
Если вы хотите отправить аргументы и получить результат из второго фрагмента, вы можете использовать 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");
}
Различные подходы (2023 г.):
- Использование графа навигации: просто установите аргумент для направления возврата (от фрагмента диалога к его родительскому элементу) и получите его в пункт назначения.
Отправить из диалога (java):NavHostFragment.navigate(this, YourDialogName+Directions .action+---+To---(DefinedArgument)));
Получите в родителе (java):dialogResult = NameOfFragmentArgs.fromBundle(requireArguments()).get+DefinedArgument;
- использование графика навигации и ViewModel+LiveData (рекомендуется)
для этого подхода есть полный пример на веб-сайте CommonsWare.
Использование намерения и пакета
Используя собственный пользовательский интерфейс для диалога , вы получаете прямой доступ к
setOnClickListener
метод для положительных и отрицательных кнопок внутри фрагмента;Реализация определенного интерфейса, например
setOnYes
в целевом фрагменте/действииС использованием
FragmentManager
иsetFragmentResult(String, Bundle)
Посмотреть этот пост
Отмеченный ответ устарел. Используйте новый API результатов фрагментов
но вместо:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setFragmentResultListener("requestKey") { requestKey, bundle ->
val result = bundle.getString("bundleKey")
}
}
которые работают для действий и стандартной связи между корневыми фрагментами, используйте:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
childFragmentManager.setFragmentResultListener("requestKey",this/*lifecycleOwner*/) { requestKey, bundle ->
val result = bundle.getString("bundleKey")
}
}
и покажиdialogFragment
нравиться:
MyDialogFragment().show(childFragmentManager, "tag")
TL;DR — используйте этот класс AppDialog как для передачи данных в DialogFragment, так и для получения результата.
Детальное объяснение:
- Предпосылка — Фрагменты уничтожаются и воссоздаются при изменении конфигурации. Просмотрите модели. При использовании Dialog рекомендуется обернуть его в DialogFragment, чтобы, когда пользователь поворачивает устройство и меняет ориентацию, Dialog не исчезал неожиданно (DialogFragment воссоздаст его и повторно отобразит).
- Ограничение (отсюда и этот вопрос). Способ работы DialogFragment заключается в том, что он берет класс, который ему нужно будет повторно создать при изменении конфигурации, - это означает, что нельзя иметь параметры конструктора для подкласса для передачи параметров, и обычно нужно сделать пользовательские обратные вызовы через модель представления, чтобы передать результат диалога. Обычно это означает новый подкласс для каждого диалога.
- Решение. Чтобы помочь со всем этим, на помощь приходит этот пользовательский фрагмент AppDialog — параметры хранятся в памяти (аналогично модели представления, вы можете думать о ней как о крошечной пользовательской модели представления, которая хранит T в памяти и использует ее для пересоздания диалога об изменении конфигурации) до тех пор, пока фрагмент диалога не будет закрыт. Правильный способ обратного вызова - через модель представления. Если фрагмент, который показывает AppDialog, то у вас, вероятно, уже есть модель представления, и вы можете ссылаться на нее из лямбды, используемой для создания диалога, — это означает дополнительную сильную ссылку на модель представления, пока фрагмент диалога не будет отклонен.
- Пример — см. примеры, в которых простой Dialog подвергается рефакторингу, чтобы использовать этот служебный класс AppDialog как для получения параметра, так и для обратного вызова viewModel для уведомления о результате.
Вспомогательный класс:
class AppDialog<T>: DialogFragment() {
companion object {
fun<T> buildDialog(params: T? = null, builder: AppDialogLambda<T>): AppDialog<T> {
// Setup arguments
val args = Bundle()
args.putInt("key", pushDialogArgs(params, builder))
// Instantiate
val fragment = AppDialog<T>()
fragment.arguments = args
return fragment
}
// --------------------
// Dialog Arguments
private var lastKey: Int = 0
private val dialogArgs = mutableMapOf<Int, Pair<Any?, AppDialogLambda<*>>>()
private fun pushDialogArgs(params: Any?, builder: AppDialogLambda<*>): Int {
dialogArgs[lastKey] = params to builder
return lastKey++
}
private fun getDialogArgs(key: Int): Pair<Any?, AppDialogLambda<*>> {
return dialogArgs[key]!!
}
private fun deleteDialogArgs(key: Int) {
dialogArgs.remove(key)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Get arguments
val argKey = requireArguments().getInt("key")
val (params, builder) = getDialogArgs(argKey)
// We are getting back our arguments we passed AppDialog.buildDialog and
// the type is guaranteed to be the same. Silence this warning
@Suppress("UNCHECKED_CAST")
return (builder as AppDialogLambda<T>)(this, params as T?)
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
val argKey = requireArguments().getInt("key")
deleteDialogArgs(argKey)
}
}
Пример использования ( после ):
val info = mapOf("message" to "${error.description}\n\nPlease check your Internet connection and try again.")
AppDialog.buildDialog(info) { fragment, params ->
fragment.isCancelable = false // since we are in a DialogFragment
AlertDialog.Builder(fragment.context)
.setTitle("Terms Of Service Failed To Load")
.setMessage(params!!["message"])
.setPositiveButton("Retry") { _, _ ->
// Update the view model instead of calling UserTOSFragment directly
// as the fragment may be destroyed and recreated
// on configuration changes. The viewModel will stay alive.
viewModel.onTermsOfServiceReload()
}
.setNegativeButton("Cancel") { _, _ ->
viewModel.onTermsOfServiceDeclined()
fragment.findNavController().popBackStack()
}.create()
}.show(parentFragmentManager, "TOS Failed Dialog")
Пример использования ( до ): без использования DialogFragment(для иллюстрации не делайте этого, это плохая практика, так как диалоговое окно будет уничтожено при изменении конфигурации), код внутри UserTOSFragment.kt — обратите внимание на код, используемый для прямого вызова UserTOSFragment. loadContent() при повторной попытке. Это нужно переписать, чтобы вместо этого вызывать viewModel.onTermsOfServiceDeclined() в приведенном выше примере:
AlertDialog.Builder(context)
.setTitle("Terms Of Service Failed To Load")
.setMessage("${error.description}\n\nPlease check your Internet connection and try again.")
.setPositiveButton("Retry") { _, _ ->
loadContent()
}
.setCancelable(false)
.setNegativeButton("Cancel") { _, _ ->
viewModel.onTermsOfServiceDeclined()
findNavController().popBackStack()
}
.show()
На фрагменте диалога
class AbcDialogFragment(private val ondata: (data: String) -> Unit) : DialogFragment() {}
На фрагменте хоста / Активность
val abcDialogFragment = AbcDialogFragment(ondata = {data-> })
a.
bcDialogFragment.show(requireActivity().supportFragmentManager,
"TAG")
а во фрагменте диалога вы можете вызвать onData, когда фрагмент диалога закрыт, или любые прослушиватели щелчков.
Просто чтобы это было одним из вариантов (так как никто еще не упомянул об этом) - вы можете использовать шину событий, такую как Otto. Итак, в диалоге вы делаете:
bus.post(new AnswerAvailableEvent(42));
И пусть ваш абонент (Activity или Fragment) подписывается на него:
@Subscribe public void answerAvailable(AnswerAvailableEvent event) {
// TODO: React to the event somehow!
}