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

Android 使用新的体系结构组件ViewModel在片段之间共享数据,android,android-fragments,android-activity,master-detail,android-architecture-components,Android,Android Fragments,Android Activity,Master Detail,Android Architecture Components,在上一次谷歌IO上,谷歌发布了一些新arch组件的预览,其中之一是ViewModel 在中,google展示了此组件的一种可能用途: 一个活动中的两个或多个片段需要修改是很常见的 相互交流。这从来都不是小事,因为这两个片段 需要定义一些接口描述,并且所有者活动必须 把两者绑在一起。此外,两个片段都必须处理这个案例 另一个片段尚未创建或不可见 使用ViewModel对象可以解决这个常见的难点。 想象一个主细节片段的常见情况,我们有一个 用户从一个列表和另一个列表中选择一个项目的片段 显示所选项目内

在上一次谷歌IO上,谷歌发布了一些新arch组件的预览,其中之一是ViewModel

在中,google展示了此组件的一种可能用途:

一个活动中的两个或多个片段需要修改是很常见的 相互交流。这从来都不是小事,因为这两个片段 需要定义一些接口描述,并且所有者活动必须 把两者绑在一起。此外,两个片段都必须处理这个案例 另一个片段尚未创建或不可见

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

这些片段可以使用其活动范围共享一个ViewModel 处理这个通讯

并显示了一个实现示例:

public class SharedViewModel extends ViewModel {
    private final SavedStateHandle state;

    public SharedViewModel(SavedStateHandle state) {
        this.state = state;
    }

    private final MutableLiveData<Item> selected = state.getLiveData("selected");

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}

public class MasterFragment extends Fragment {
    private SharedViewModel model;

    @Override
    protected void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        model = new ViewModelProvider(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    @Override
    protected void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        SharedViewModel model = new ViewModelProvider(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
           // update UI
        });
    }
}
公共类SharedViewModel扩展了ViewModel{
私有最终SavedStateHandle状态;
public SharedViewModel(SavedStateHandle状态){
this.state=状态;
}
private final MutableLiveData selected=state.getLiveData(“selected”);
公共作废选择(项目){
已选择。设置值(项目);
}
公共LiveData getSelected(){
返回选中的;
}
}
公共类主片段扩展片段{
私有共享模型;
@凌驾
已创建受保护的空视图(视图、捆绑包savedInstanceState){
super.onViewCreated(视图,savedInstanceState);
model=newviewmodelprovider(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(项->{
型号。选择(项目);
});
}
}
公共类DetailFragment扩展了片段{
@凌驾
已创建受保护的空视图(视图、捆绑包savedInstanceState){
super.onViewCreated(视图,savedInstanceState);
SharedViewModel model=newviewmodelprovider(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(此,{item->
//更新用户界面
});
}
}
对于不需要那些用于片段的接口就可以通过活动进行通信,我感到非常兴奋

但谷歌的例子并没有确切地显示我如何从master调用细节片段


我仍然必须使用将由活动实现的方法,该方法将调用fragmentManager.replace(…),或者有另一种方法使用新的体系结构来实现这一点?

在使用附加到被视为容器的活动的回调之前。
那个回调是两个片段之间的中间人。 以前的解决方案的缺点是:

  • 活动必须进行回调,这意味着需要进行大量的工作 活动
  • 两个片段紧密耦合,以后很难更新或更改逻辑
有了新的ViewModel(支持LiveData),您就有了一个优雅的解决方案。它现在扮演中间人的角色,您可以将其生命周期附加到活动中

  • 两个片段之间的逻辑和数据现在显示在ViewModel中
  • 两个片段从ViewModel获取数据/状态,因此它们不需要相互了解
  • 此外,借助LiveData的强大功能,您可以根据主片段的更改以反应式方式更改细节片段,而不是以前的回调方式
您现在完全摆脱了与活动和相关片段紧密耦合的回调。

我强烈推荐你通过。在步骤5中,您可以找到一个很好的例子。

您可以像这样设置从细节片段到主片段的值

model.selected.setValue(item)

于2017年6月12日更新,

Android Official提供了一个简单、精确的示例,说明ViewModel如何在主详细信息模板上工作,您应该先看看它。

作为@CommonWare,@Quang-Nguyen method,Yigit的目的不是从大师到细节,而是更好地使用中间人模式。但是,如果您想进行一些片段事务,则应该在活动中完成。此时,ViewModel类应该是活动中的静态类,并且可能包含一些丑陋的回调来回调活动以生成片段事务

我已经试着实现了这一点,并为此做了一个简单的项目。你可以看看。大部分代码引用自Google IO 2017,也是结构。

我没有使用Master-Detail片段来实现该组件,但是使用旧的(ViewPager中片段之间的通信)逻辑应该是相同的

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

你想在中间人发送和接收什么,他们应该只在视图模型中发送和接收。
  • 在fragment类中,修改似乎不太多。因为它只将实现从“接口回调”更改为“侦听和响应ViewModel”
  • 视图模型初始化似乎很重要,可能会在活动中调用
  • 使用MutableLiveData使源仅在活动中同步
  • 1.寻呼机活动

    public class PagerActivity extends AppCompatActivity {
        /**
         * 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 = new ViewModelProvider(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(它应该有一个更好的名字,而不是这个)

    公共类PagerAgentViewModel扩展了ViewModel{
    私有最终SavedStateHandle状态;
    私有最终可变LiveData messageContainerA;
    私有最终可变LiveData messageContainerB;
    公共页面RagentViewModel(SavedStateHandle状态){
    this.state=状态;
    messageContainerA=state.getLiveData(“默认消息”);
    messageContainerB=state.getLiveData(“默认消息”);
    }
    public void sendMessageToB(字符串消息)
    {
    messageContainerB.setValue(msg);
    }
    公共无效sendMessageToA
    
    public class PagerAgentViewModel extends ViewModel {
        private final SavedStateHandle state;
        private final MutableLiveData<String> messageContainerA;
        private final MutableLiveData<String> messageContainerB;
    
        public PagerAgentViewModel(SavedStateHandle state) {
            this.state = state;
    
            messageContainerA = state.getLiveData("Default Message");
            messageContainerB = state.getLiveData("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;
        }
    }
    
    public class BlankFragmentA extends Fragment {
    
        private PagerAgentViewModel viewModel;
    
        public BlankFragmentA() {
            // Required empty public constructor
        }
    
        @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
    
            viewModel = new ViewModelProvider(getActivity()).get(PagerAgentViewModel.class);
    
    
            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) {
                    viewModel.sendMessageToB("Hello B");
                }
            });
    
            //setup the listener for the fragment A
            viewModel.getMessageContainerA().observe(getViewLifecycleOwner(), 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);
            return view;
        }
    
    }
    
    public class BlankFragmentB extends Fragment {
     
        public BlankFragmentB() {
            // Required empty public constructor
        }
    
        @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
    
            viewModel = new ViewModelProvider(getActivity()).get(PagerAgentViewModel.class);
    
            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) {
                    viewModel.sendMessageToA("Hello A");
                }
            });
    
            //setup the listener for the fragment B
            viewModel.getMessageContainerB().observe(getViewLifecycleOwner(), 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);
            return view;
        }
    
    }
    
    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;
        }
    
    }
    
    SharedViewModel stepViewModel = ViewModelProviders.of(this).get("step", SharedViewModel.class);
    stepViewModel.setListener(this);
    
    @Override
    public void selected(Step item) {
        Log.d(TAG, "selected: "+item);
    }
    
    stepViewModel = ViewModelProviders.of(getActivity()).get("step", SharedViewModel.class);
    
    stepViewModel.select(step);
    
    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;
            }
        }
    }
    
    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();
        }
    }
    
    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();
        }
    }
    
    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);
       }
    }
    
    dependencies {
      def lifecycle_version = "1.1.1"
      implementation "android.arch.lifecycle:extensions:$lifecycle_version"
    }
    
    // Use the 'by activityViewModels()' Kotlin property delegate
    // from the fragment-ktx artifact
    private val model: SharedViewModel by activityViewModels()
    
      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)
              }
          }
      }