Обмен данными между фрагментами с использованием нового компонента архитектуры ViewModel
на последнем Google IO Google выпустил предварительный просмотр некоторых новых компонентов arch, один из которых, ViewModel.
на docs google показывает одно из возможных применений этого компонента:
очень распространено, что два или более фрагментов в деятельности должны общаются друг с другом. Это никогда не тривиально, как оба фрагмента необходимо определить некоторое описание интерфейса, и действие владельца должно свяжите их вместе. Более того, оба фрагменты должны обрабатывать дело где другой фрагмент еще не создан или не видны.
эта общая точка боли может быть решена с помощью объектов ViewModel. Представьте себе общий случай фрагментов master-detail, где у нас есть фрагмент, в котором пользователь выбирает элемент из списка, а другой фрагмент, отображающий содержимое выбранного элемента.
эти фрагменты могут совместно использовать 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 onActivityCreated() {
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends LifecycleFragment {
public void onActivityCreated() {
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// update UI
});
}
}
Я был очень взволнован возможностью не нуждаться в тех интерфейсах, которые используются для фрагментов для связи через активность.
но пример Google не показывает точно, как я бы назвал фрагмент детали из master.
Мне все равно придется использовать интерфейс это будет реализовано деятельностью, которая будет вызывать fragmentManager.заменять.(..), или есть другой способ сделать это, используя новую архитектуру?
6 ответов
Обновлено 6/12/2017,
Android официальный предоставить простой, точный пример к примеру, как ViewModel работает на шаблоне Master-Detail, вы должны взглянуть на него в первую очередь.обмен данными между фрагментами
как @CommonWare, @Quang Nguyen methioned, это не цель для Yigit сделать вызов от мастера к деталям, но лучше использовать шаблон среднего человека. Но если вы хотите сделать какой-то фрагмент транзакция, это должно быть сделано в деятельности. В этот момент класс ViewModel должен быть статическим классом в Activity и может содержать некоторый уродливый обратный вызов для вызова activity для транзакции фрагмента.
Я пытался реализовать это и сделать простой проект об этом. Можете взглянуть. Большая часть кода ссылается на Google IO 2017, а также на структуру. https://github.com/charlesng/SampleAppArch
Я не использую Master Подробно фрагмент для реализации компонента, но старый (связь между фрагментом в ViewPager. Логика должна быть такой же.
но я нашел что-то важное, используя эти компоненты
- то, что вы хотите отправить и получить в среднем человеке, они должны быть отправлены и получены в виде модели только
- модификация кажется не слишком большой в классе fragment. Поскольку он только изменяет реализацию с "обратного вызова интерфейса" на "Слушая и отвечая ViewModel"
- инициализация модели представления кажется важной и, вероятно, будет вызвана в действии.
- использование MutableLiveData для синхронизации источника только в действии.
1.Активность Пейджера
public class PagerActivity extends LifecycleActivity {
/**
* The pager widget, which handles animation and allows swiping horizontally to access previous
* and next wizard steps.
*/
private ViewPager mPager;
private PagerAgentViewModel pagerAgentViewModel;
/**
* The pager adapter, which provides the pages to the view pager widget.
*/
private PagerAdapter mPagerAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pager);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
mPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
pagerAgentViewModel = ViewModelProviders.of(this).get(PagerAgentViewModel.class);
pagerAgentViewModel.init();
}
/**
* A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in
* sequence.
*/
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
...Pager Implementation
}
}
2.PagerAgentViewModel (он заслуживал лучшего названия, чем это)
public class PagerAgentViewModel extends ViewModel {
private MutableLiveData<String> messageContainerA;
private MutableLiveData<String> messageContainerB;
public void init()
{
messageContainerA = new MutableLiveData<>();
messageContainerA.setValue("Default Message");
messageContainerB = new MutableLiveData<>();
messageContainerB.setValue("Default Message");
}
public void sendMessageToB(String msg)
{
messageContainerB.setValue(msg);
}
public void sendMessageToA(String msg)
{
messageContainerA.setValue(msg);
}
public LiveData<String> getMessageContainerA() {
return messageContainerA;
}
public LiveData<String> getMessageContainerB() {
return messageContainerB;
}
}
3.BlankFragmentA
public class BlankFragmentA extends LifecycleFragment {
public BlankFragmentA() {
// Required empty public constructor
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//setup the listener for the fragment A
ViewModelProviders.of(getActivity()).get(PagerAgentViewModel.class).getMessageContainerA().observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String msg) {
textView.setText(msg);
}
});
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_blank_a, container, false);
textView = (TextView) view.findViewById(R.id.fragment_textA);
// set the onclick listener
Button button = (Button) view.findViewById(R.id.btnA);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewModelProviders.of(getActivity()).get(PagerAgentViewModel.class).sendMessageToB("Hello B");
}
});
return view;
}
}
4.BlankFragmentB
public class BlankFragmentB extends LifecycleFragment {
public BlankFragmentB() {
// Required empty public constructor
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//setup the listener for the fragment B
ViewModelProviders.of(getActivity()).get(PagerAgentViewModel.class).getMessageContainerB().observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String msg) {
textView.setText(msg);
}
});
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_blank_b, container, false);
textView = (TextView) view.findViewById(R.id.fragment_textB);
//set the on click listener
Button button = (Button) view.findViewById(R.id.btnB);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewModelProviders.of(getActivity()).get(PagerAgentViewModel.class).sendMessageToA("Hello A");
}
});
return view;
}
}
перед использованием обратного вызова, который присоединяется к активности, которая рассматривается как контейнер.
Этот звонок-посредник между двумя фрагментами.
Плохие вещи об этом предыдущем решении:
- активность должна нести обратный вызов, это означает много работы для Деятельность.
- два фрагмента связаны плотно, трудно обновить или изменить логику позже.
с новой ViewModel (с поддержкой LiveData), вы есть элегантное решение. Теперь он играет роль среднего человека, который вы можете прикрепить его жизненный цикл к деятельности.
- логика и данные между двумя фрагментами теперь лежат в ViewModel.
- два фрагмента получают данные / состояние от ViewModel, поэтому им не нужно знать друг друга.
- кроме того, с помощью LiveData вы можете изменить фрагмент детали на основе изменений Основного фрагмента в реактивном подходе вместо предыдущего обратного вызова путь.
теперь вы полностью избавляетесь от обратного вызова, который плотно соединяется как с активностью, так и с соответствующими фрагментами.
Я очень рекомендую вам через лаборатория кода Google. На шаге 5 Вы можете найти хороший пример этого.
я реализовал что-то похожее на то, что вы хотите, моя viewmodel содержит объект LiveData, который содержит состояние перечисления, и когда вы хотите изменить фрагмент от master до details (или наоборот), вы вызываете функции ViewModel, которые изменяют значение livedata, и активность знает, чтобы изменить фрагмент, потому что он наблюдает livedata объект.
TestViewModel:
public class TestViewModel extends ViewModel {
private MutableLiveData<Enums.state> mState;
public TestViewModel() {
mState=new MutableLiveData<>();
mState.setValue(Enums.state.Master);
}
public void onDetail() {
mState.setValue(Enums.state.Detail);
}
public void onMaster() {
mState.setValue(Enums.state.Master);
}
public LiveData<Enums.state> getState() {
return mState;
}
}
перечисления:
public class Enums {
public enum state {
Master,
Detail
}
}
TestActivity:
public class TestActivity extends LifecycleActivity {
private ActivityTestBinding mBinding;
private TestViewModel mViewModel;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding=DataBindingUtil.setContentView(this, R.layout.activity_test);
mViewModel=ViewModelProviders.of(this).get(TestViewModel.class);
mViewModel.getState().observe(this, new Observer<Enums.state>() {
@Override
public void onChanged(@Nullable Enums.state state) {
switch(state) {
case Master:
setMasterFragment();
break;
case Detail:
setDetailFragment();
break;
}
}
});
}
private void setMasterFragment() {
MasterFragment masterFragment=MasterFragment.newInstance();
getSupportFragmentManager().beginTransaction().replace(R.id.frame_layout, masterFragment,"MasterTag").commit();
}
private void setDetailFragment() {
DetailFragment detailFragment=DetailFragment.newInstance();
getSupportFragmentManager().beginTransaction().replace(R.id.frame_layout, detailFragment,"DetailTag").commit();
}
@Override
public void onBackPressed() {
switch(mViewModel.getState().getValue()) {
case Master:
super.onBackPressed();
break;
case Detail:
mViewModel.onMaster();
break;
}
}
}
MasterFragment:
public class MasterFragment extends Fragment {
private FragmentMasterBinding mBinding;
public static MasterFragment newInstance() {
MasterFragment fragment=new MasterFragment();
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mBinding=DataBindingUtil.inflate(inflater,R.layout.fragment_master, container, false);
mBinding.btnDetail.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final TestViewModel viewModel=ViewModelProviders.of(getActivity()).get(TestViewModel.class);
viewModel.onDetail();
}
});
return mBinding.getRoot();
}
}
DetailFragment:
public class DetailFragment extends Fragment {
private FragmentDetailBinding mBinding;
public static DetailFragment newInstance() {
DetailFragment fragment=new DetailFragment();
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mBinding=DataBindingUtil.inflate(inflater,R.layout.fragment_detail, container, false);
mBinding.btnMaster.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final TestViewModel viewModel=ViewModelProviders.of(getActivity()).get(TestViewModel.class);
viewModel.onMaster();
}
});
return mBinding.getRoot();
}
}
в конечном итоге я использую собственную ViewModel для удержания прослушивателя, который запустит метод Activity. Похожие на старый способ но, как я уже сказал, передавая слушателя ViewModel вместо фрагмента. Поэтому моя ViewModel выглядела так:
public class SharedViewModel<T> extends ViewModel {
private final MutableLiveData<T> selected = new MutableLiveData<>();
private OnSelectListener<T> listener = item -> {};
public interface OnSelectListener <T> {
void selected (T item);
}
public void setListener(OnSelectListener<T> listener) {
this.listener = listener;
}
public void select(T item) {
selected.setValue(item);
listener.selected(item);
}
public LiveData<T> getSelected() {
return selected;
}
}
в StepMasterActivity я получаю ViewModel и устанавливаю его в качестве слушателя:
StepMasterActivity.класс:
SharedViewModel stepViewModel = ViewModelProviders.of(this).get("step", SharedViewModel.class);
stepViewModel.setListener(this);
...
@Override
public void selected(Step item) {
Log.d(TAG, "selected: "+item);
}
...
в фрагменте я просто получить в ViewModel
stepViewModel = ViewModelProviders.of(getActivity()).get("step", SharedViewModel.class);
и звонок:
stepViewModel.select(step);
Я проверил его поверхностно, и это сработало. По мере реализации других функций, связанных с этим, я буду знать о любых проблемах, которые могут возникнуть.
Я нашел аналогичное решение, как и другие в соответствии с Google codelabs пример. У меня есть два фрагмента, где один из них ждет изменения объекта в другом и продолжает свой процесс с обновленным объектом.
для этого подхода вам понадобится класс ViewModel, как показано ниже:
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel;
import yourPackage.YourObjectModel;
public class SharedViewModel extends ViewModel {
public MutableLiveData<YourObjectModel> item = new MutableLiveData<>();
public YourObjectModel getItem() {
return item.getValue();
}
public void setItem(YourObjectModel item) {
this.item.setValue(item);
}
}
и фрагмент слушателя должен выглядеть так:
public class ListenerFragment extends Fragment{
private SharedViewModel model;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.item.observe(getActivity(), new Observer<YourObjectModel>(){
@Override
public void onChanged(@Nullable YourObjectModel updatedObject) {
Log.i(TAG, "onChanged: recieved freshObject");
if (updatedObject != null) {
// Do what you want with your updated object here.
}
}
});
}
}
наконец, фрагмент updater может быть следующим:
public class UpdaterFragment extends DialogFragment{
private SharedViewModel model;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
}
// Call this method where it is necessary
private void updateViewModel(YourObjectModel yourItem){
model.setItem(yourItem);
}
}
это хорошо отметить, что фрагмент updater может быть любой формой фрагментов (не только DialogFragment), и для использования этих компонентов архитектуры вы должны иметь эти строки кодов в сборке приложения.файл gradle. источник
dependencies {
def lifecycle_version = "1.1.1"
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
}
вы можете установить значения из фрагмента детали в мастер-фрагмент, как это
model.selected.setValue(item)