首页 文章

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

提问于
浏览
26

在Last 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调用详细信息片段 .

我仍然必须使用将由活动实现的an interface,它将调用fragmentManager.replace(...),或者使用新架构还有另一种方法吗?

6 回答

  • 2

    Updated on 6/12/2017,

    Android Official provide a simple, precise example to example how the ViewModel works on Master-Detail template, you should take a look on it first.Share data between fragments

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

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

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

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

    • 您想在中间人发送和接收的内容,只能在“查看模型”中发送和接收

    • 片段类中的修改似乎不太多 . 因为它只将实现从"Interface callback"更改为"Listening and responding ViewModel"

    • 视图模型初始化似乎很重要,可能会在活动中调用 .

    • 使用MutableLiveData使源仅在活动中同步 .

    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;
        }
    
    }
    
  • 5

    我实现了类似于你想要的东西,我的viewmodel包含包含Enum状态的LiveData对象,当你想要将片段从master更改为细节(或反过来)时,你调用ViewModel函数来改变livingata值,并且活动知道更改片段,因为它正在观察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();
        }
    }
    
  • 26

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

    • Activity必须进行回调,这对于Activity来说意味着很多工作 .

    • 两个片段紧密耦合,以后很难更新或更改逻辑 .

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

    • 两个片段之间的逻辑和数据现在布局在ViewModel中 .

    • Two Fragment从ViewModel获取数据/状态,因此他们不需要彼此了解 .

    • 此外,借助LiveData的强大功能,您可以根据主动片段在被动方式中的变化而不是之前的回调方式来更改细节片段 .

    你现在完全摆脱了与Activity和相关碎片紧密耦合的回调 .
    我强烈建议你通过Google's code lab . 在第5步中,您可以找到一个很好的例子 .

  • 4

    根据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. 
                }
            }
        });
    }
    }
    

    最后,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),对于使用这些体系结构组件,您应该在app build.gradle文件中包含这些代码行 . source

    dependencies {
      def lifecycle_version = "1.1.1"
      implementation "android.arch.lifecycle:extensions:$lifecycle_version"
    }
    
  • 1

    我最终使用自己的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);
    

    我表面上测试它,它的工作原理 . 当我开始实现与此相关的其他功能时,我会发现可能出现的任何问题 .

  • -2

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

    model.selected.setValue(item)
    

相关问题