Как отправить данные из диалогового окна в фрагмент?
У меня есть фрагмент, который открывает Dialogfragment
, чтобы получить пользовательский ввод (строка и целое число). Как отправить эти две вещи обратно в фрагмент?
Вот мой DialogFragment:
public class DatePickerFragment extends DialogFragment {
String Month;
int Year;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
getDialog().setTitle(getString(R.string.Date_Picker));
View v = inflater.inflate(R.layout.date_picker_dialog, container, false);
Spinner months = (Spinner) v.findViewById(R.id.months_spinner);
ArrayAdapter<CharSequence> monthadapter = ArrayAdapter.createFromResource(getActivity(),
R.array.Months, R.layout.picker_row);
months.setAdapter(monthadapter);
months.setOnItemSelectedListener(new OnItemSelectedListener(){
@Override
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int monthplace, long id) {
Month = Integer.toString(monthplace);
}
public void onNothingSelected(AdapterView<?> parent) {
}
});
Spinner years = (Spinner) v.findViewById(R.id.years_spinner);
ArrayAdapter<CharSequence> yearadapter = ArrayAdapter.createFromResource(getActivity(),
R.array.Years, R.layout.picker_row);
years.setAdapter(yearadapter);
years.setOnItemSelectedListener(new OnItemSelectedListener(){
@Override
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int yearplace, long id) {
if (yearplace == 0){
Year = 2012;
}if (yearplace == 1){
Year = 2013;
}if (yearplace == 2){
Year = 2014;
}
}
public void onNothingSelected(AdapterView<?> parent) {}
});
Button button = (Button) v.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
getDialog().dismiss();
}
});
return v;
}
}
Мне нужно отправить данные после нажатия кнопки и до getDialog().dismiss()
Вот фрагмент, который необходимо отправить данным:
public class CalendarFragment extends Fragment {
int Year;
String Month;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int position = getArguments().getInt("position");
String[] categories = getResources().getStringArray(R.array.categories);
getActivity().getActionBar().setTitle(categories[position]);
View v = inflater.inflate(R.layout.calendar_fragment_layout, container, false);
final Calendar c = Calendar.getInstance();
SimpleDateFormat month_date = new SimpleDateFormat("MMMMMMMMM");
Month = month_date.format(c.getTime());
Year = c.get(Calendar.YEAR);
Button button = (Button) v.findViewById(R.id.button);
button.setText(Month + " "+ Year);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
new DatePickerFragment().show(getFragmentManager(), "MyProgressDialog");
}
});
return v;
}
}
поэтому, как только пользователь выбирает дату в Dialogfragment
, он должен вернуть месяц и год.
Затем текст на кнопке должен измениться на месяц и год, указанные пользователем.
Ответы
Ответ 1
ПРИМЕЧАНИЕ. Помимо одного или двух вызовов, специфичных для фрагмента Android, это общий подход к реализации обмена данными между слабосвязанными компонентами. Вы можете безопасно использовать этот подход для обмена данными буквально с чем угодно, будь то фрагменты, действия, диалоги или любые другие элементы вашего приложения.
Вот рецепт:
- Создайте
interface
(то есть с именем MyContract
), содержащий сигнатуру метода для передачи данных, то есть methodToPassData(... data);
.
- Убедитесь, что ваш
DialogFragment
полностью заполнен этим контрактом (что обычно означает реализацию желаемого интерфейса): class MyFragment extends Fragment implements MyContract {....}
- При создании
DialogFragment
установите ваш вызывающий Fragment
в качестве целевого фрагмента, вызвав myDialogFragment.setTargetFragment(this, 0);
. Это объект, с которым вы будете разговаривать позже.
- В вашем
DialogFragment
получите этот вызывающий фрагмент, вызвав getTargetFragment();
, и приведите возвращенный объект к интерфейсу контракта, созданному вами на шаге 1, выполнив: MyContract mHost = (MyContract)getTargetFragment();
. Приведение позволяет нам гарантировать, что целевой объект реализует необходимый контракт, и мы можем ожидать, что methodToPassData()
будет там. Если нет, то вы получите обычный ClassCastException
. Это обычно не должно происходить, если вы не выполняете слишком много операций копирования-вставки :) Если ваш проект использует внешний код, библиотеки или плагины и т.д., И в таком случае вам лучше отловить исключение и сказать пользователю, т.е. плагин не совместим вместо просто дайте приложению упасть.
- Когда придет время отправлять данные обратно, вызовите
methodToPassData()
для объекта, который вы получили ранее: ((MyContract)getTargetFragment()).methodToPassData(data);
. Если ваш onAttach()
уже преобразует и назначает целевой фрагмент переменной класса (т.е. mHost
), тогда этот код будет просто mHost.methodToPassData(data);
.
- Вуаля.
Вы только что успешно передали свои данные из диалога обратно к вызывающему фрагменту.
Ответ 2
Здесь другой рецепт без использования интерфейса. Просто используя setTargetFragment
и Bundle
для передачи данных между DialogFragment и Fragment.
public static final int DATEPICKER_FRAGMENT = 1; // class variable
1.
Вызовите DialogFragment
, как показано ниже:
// create dialog fragment
DatePickerFragment dialog = new DatePickerFragment();
// optionally pass arguments to the dialog fragment
Bundle args = new Bundle();
args.putString("pickerStyle", "fancy");
dialog.setArguments(args);
// setup link back to use and display
dialog.setTargetFragment(this, DATEPICKER_FRAGMENT);
dialog.show(getFragmentManager().beginTransaction(), "MyProgressDialog")
2.
Используйте дополнительный Bundle
в Intent
в DialogFragment
, чтобы передать информацию обратно целевому фрагменту. Следующий код в Button#onClick()
событии DatePickerFragment
передает строку и целое число.
Intent i = new Intent()
.putExtra("month", getMonthString())
.putExtra("year", getYearInt());
getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, i);
dismiss();
3.
Используйте метод CalendarFragment
onActivityResult()
для чтения значений:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case DATEPICKER_FRAGMENT:
if (resultCode == Activity.RESULT_OK) {
Bundle bundle = data.getExtras();
String mMonth = bundle.getString("month", Month);
int mYear = bundle.getInt("year");
Log.i("PICKER", "Got year=" + year + " and month=" + month + ", yay!");
} else if (resultCode == Activity.RESULT_CANCELED) {
...
}
break;
}
}
Ответ 3
Хороший совет - использовать подход ViewModel и LiveData, который является наилучшим способом. Ниже приведен пример кода о том, как обмениваться данными между фрагментами:
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
model.selected.observe(this, Observer<Item> { item ->
// Update the UI
})
}
}
Попробуйте эти новые компоненты, чтобы помочь нам, я думаю, что это более эффективно, чем другой подход.
Узнайте больше об этом: https://developer.android.com/topic/libraries/architecture/viewmodel
Ответ 4
Здесь подход, иллюстрирующий ответ марцина, реализован в kotlin.
1. Создайте интерфейс, в котором есть метод для передачи данных в вашем классе dialogFragment.
interface OnCurrencySelected{
fun selectedCurrency(currency: Currency)
}
2. Добавьте свой интерфейс в конструктор dialogFragment.
class CurrencyDialogFragment(val onCurrencySelected :OnCurrencySelected) :DialogFragment() {}
3. Теперь заставьте ваш фрагмент реализовать интерфейс, который вы только что создали
class MyFragment : Fragment(), CurrencyDialogFragment.OnCurrencySelected {
override fun selectedCurrency(currency: Currency) {
//this method is called when you pass data back to the fragment
}}
4. Затем, чтобы показать ваш диалог
CurrencyDialogFragment(this).show(fragmentManager,"dialog")
. this
- это интерфейсный объект, с которым вы будете разговаривать, для передачи данных обратно в ваш фрагмент.
5. Когда вы хотите отправить данные обратно в свой фрагмент, просто вызовите метод для передачи данных в объект интерфейса, который вы передали в конструктор dialogFragment.
onCurrencySelected.selectedCurrency(Currency.USD)
dialog.dismiss()