使用新的体系结构组件ViewModel在片段之间共享数据

在Last Google IO上,Google发布了一些新的Arch组件预览,其中一个是ViewModel。

在文档中, Google显示了此组件的一种可能用途:

活动中的两个或更多个片段需要彼此通信是很常见的。 这绝不是微不足道的,因为两个片段都需要定义一些接口描述,并且所有者活动必须将两者绑定在一起。 而且,两个片段必须处理尚未创建或不可见的其他片段的情况。

可以使用ViewModel对象解决这个常见的痛点。 想象一下主从细节片段的常见情况,其中我们有一个片段,其中用户从列表中选择一个项目,另一个片段显示所选项目的内容。

这些片段可以使用其活动范围共享ViewModel来处理此通信。

并显示了一个实现示例:

public class SharedViewModel extends ViewModel { private final MutableLiveData selected = new MutableLiveData(); public void select(Item item) { selected.setValue(item); } public LiveData 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调用详细信息片段。

我仍然必须使用一个将由activity实现的接口 ,它将调用fragmentManager.replace(…),或者还有另一种方法可以使用新的架构来实现它?

2017年12月6日更新,

Android官方提供了一个简单,精确的示例来说明Viewmoel如何在Master-Detail模板上工作,您应该首先看一下它。 在片段之间共享数据

正如@CommonWare,@ Quang Nguyen所做的那样,Yigit的目的并不是从主人到细节进行呼叫,而是更好地使用中间人模式。 但是如果你想做一些片段事务,它应该在活动中完成。 在那一刻,viewmodel类应该作为Activity中的静态类,并且可能包含一些Ugly Callback来回调活动以进行片段事务。

我试图实现这个并做一个关于这个的简单项目。 你可以看一下。 大多数代码都是从Google IO 2017引用的,也就是结构。 https://github.com/charlesng/SampleAppArch

我不使用Master Detail Fragment来实现组件,而是使用旧的组件(ViewPager中的片段之间的通信。)逻辑应该是相同的。

但我发现使用这些组件有些重要

  1. 您希望在中间人中发送和接收的内容,只能在“查看模型”中发送和接收
  2. 片段类中的修改似乎不太多。 因为它只将实现从“接口回调”更改为“侦听和响应ViewModel”
  3. 视图模型初始化似乎很重要,可能会在活动中调用。
  4. 使用MutableLiveData使源仅在活动中同步。

1.Pager活动

 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 messageContainerA; private MutableLiveData 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 getMessageContainerA() { return messageContainerA; } public LiveData 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() { @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() { @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包含包含Enum状态的LiveData对象,当你想要将片段从master更改为细节(或反向)时,你调用ViewModel函数来改变livesata值,并且活动知道更改片段,因为它正在观察livingata对象。

TestViewModel:

 public class TestViewModel extends ViewModel { private MutableLiveData 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 getState() { return mState; } } 

枚举:

 public class Enums { public enum state { Master, Detail } } 

测试活动:

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

在使用附加到Activity的回调之前,该回调被视为容器。
那个回调是两个碎片之间的中间人。 以前的解决方案的坏处是:

  • Activity必须进行回调,这意味着Activity有很多工作要做。
  • 两个片段紧密耦合,以后很难更新或更改逻辑。

使用新的ViewModel(支持LiveData),您可以获得优雅的解决方案。 它现在扮演中间人的角色,你可以将它的生命周期附加到Activity。

  • 两个片段之间的逻辑和数据现在布局在ViewModel中。
  • Two Fragment从ViewModel获取数据/状态,因此它们不需要彼此了解。
  • 此外,借助LiveData的强大function,您可以根据主动片段在被动方式中的变化而不是之前的回调方式来更改细节片段。

你现在完全摆脱了与Activity和相关碎片紧密耦合的回调。
我强烈建议您通过Google的代码实验室 。 在第5步中,您可以find一个很好的例子。

我最终使用自己的ViewModel来阻止将触发Activity方法的侦听器。 类似于旧的方式,但正如我所说,将侦听器传递给ViewModel而不是片段。 所以我的ViewModel看起来像这样:

 public class SharedViewModel extends ViewModel { private final MutableLiveData selected = new MutableLiveData<>(); private OnSelectListener listener = item -> {}; public interface OnSelectListener  { void selected (T item); } public void setListener(OnSelectListener listener) { this.listener = listener; } public void select(T item) { selected.setValue(item); listener.selected(item); } public LiveData getSelected() { return selected; } } 

在StepMasterActivity中,我获取ViewModel并将其设置为侦听器:

StepMasterActivity.class:

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

我表面上测试它,它的工作原理。 当我开始实现与此相关的其他function时,我将意识到可能发生的任何问题。

您可以像这样设置Detail Fragment到Master Fragment的值

 model.selected.setValue(item)