在最后一次Google IO上,Google发布了一些新的Arch组件预览,其中一个是ViewModel。
在docs google中显示了此组件的一种可能用途:
活动中的两个或更多片段非常常见 彼此沟通。这两个片段都不是微不足道的 需要定义一些接口描述,并且所有者活动必须 将两者绑在一起。而且,两个片段都必须处理这个案子 其他片段尚未创建或不可见的地方。
使用ViewModel对象可以解决这个常见的痛点。 想象一下主要细节片段的常见情况,我们有一个 用户从列表中选择项目和另一个项目的片段 显示所选项目内容的片段。
这些片段可以使用其活动范围共享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
});
}
}
我很高兴不需要那些用于片段的接口通过活动进行通信。
但谷歌的例子并未准确显示我将如何从主人那里调用详细信息片段。
我仍然必须使用活动实现的an interface,它将调用fragmentManager.replace(...),或者使用新架构还有另一种方法吗?
答案 0 :(得分:35)
于2017年12月6日更新,
Android官方提供了一个简单,精确的示例来说明ViewModel如何在Master-Detail模板上运行,您应首先查看它。Share data between fragments
正如@CommonWare,@ Quang Nguyen所做的那样,Yigit的目的并不是要从大师到细节进行调用,而是更好地使用中间人模式。但是如果你想做一些片段事务,它应该在活动中完成。此时,ViewModel类应该作为Activity中的静态类,并且可能包含一些Ugly Callback来回调活动以进行片段事务。
我试图实现这一点并制作一个关于此的简单项目。你可以看一下。大多数代码都是从Google IO 2017引用的,也就是结构。 https://github.com/charlesng/SampleAppArch
我不使用Master Detail Fragment来实现组件,而是使用旧的组件(ViewPager中的片段之间的通信。)逻辑应该是相同的。
但是我发现使用这些组件很重要
1.Pager Activity
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;
}
}
答案 1 :(得分:8)
如the official Google tutorial所述,您现在可以与by activityViewModels()
获得共享视图模型
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
答案 2 :(得分:6)
我实现了类似于你想要的东西,我的viewmodel包含包含Enum状态的LiveData对象,当你想要将片段从master更改为细节(或反过来)时,你调用改变livesata值的ViewModel函数,以及活动知道更改片段,因为它正在观察livingata对象。
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
}
}
测试活动:
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();
}
}
答案 3 :(得分:6)
根据Google Codelabs example,我发现了与其他解决方案相似的解决方案。 我有两个片段,其中一个片段等待另一个片段中的对象更改,并使用更新的对象继续其过程。
对于这种方法,您将需要一个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.
}
}
});
}
}
最后,更新程序片段可以像这样:
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);
}
}
值得一提的是,更新程序片段可以是任何形式的片段(不仅限于DialogFragment),对于使用这些体系结构组件,您应该在应用程序build.gradle文件中包含以下代码行。 source
dependencies {
def lifecycle_version = "1.1.1"
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
}
答案 4 :(得分:4)
在使用附加到被视为容器的Activity的回调之前 那个回调是两个碎片之间的中间人。 以前的解决方案的坏处是:
使用新的ViewModel(支持LiveData),您可以获得优雅的解决方案。它现在扮演中间人的角色,你可以将它的生命周期附加到Activity。
你现在完全摆脱了与活动和相关碎片紧密结合的回调 我强烈建议您通过Google's code lab。在第5步中,您可以找到一个很好的例子。
答案 5 :(得分:2)
我最终使用自己的ViewModel来阻止将触发Activity方法的侦听器。与old way类似,但正如我所说,将侦听器传递给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.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);
我在表面上测试了它并且它有效。当我开始实现与此相关的其他功能时,我会发现可能出现的任何问题。
答案 6 :(得分:0)
对于那些在那里使用Kotlin的人,请尝试以下方法:
将androidx ViewModel and LiveData库添加到gradle文件中
像这样在片段中调用视图模型:
class MainFragment : Fragment() {
private lateinit var viewModel: ViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// kotlin does not have a getActivity() built in method instead we use activity, which is null-safe
activity?.let {
viemModel = ViewModelProvider(it).get(SharedViewModel::class.java)
}
}
}
上述方法是一种好的做法,因为它将避免由于空指针异常而导致崩溃
编辑:作为btraas的补充:活动被编译为getActivity(),在android SDK中标记为@Nullable。 activity和getActivity()都是可访问的,并且是等效的。
答案 7 :(得分:-2)
您可以像这样设置从细节片段到主片段的值
model.selected.setValue(item)