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

ale*_*pfx 45 android master-detail android-fragments android-activity android-architecture-components

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

文档中, 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
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

我很高兴不需要那些用于片段的接口通过活动进行通信.

但Google的示例并没有准确显示我将如何从master调用详细信息片段.

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

Lon*_*ger 42

2017年6月6日更新,

Android官方提供了一个简单,精确的示例来说明ViewModel如何在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
    }

}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

  • 不推荐使用LifecycleFragment (7认同)

Dan*_*iel 19

正如谷歌官方教程中所写,现在你可以获得一个共享视图模型by activityViewModels()

// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
Run Code Online (Sandbox Code Playgroud)

  • Kotlin 急需的解决方案。如果使用“viewModels()”而不是“activityViewModels()”,则不会共享数据。 (8认同)
  • 文档中没有说明,但我们是否必须首先在容器活动中初始化 ViewModel ?仅通过 ActivityViewModels() 在两个片段中添加 SharedViewModel 并不能为我创建 ViewModel。 (2认同)

Ami*_*dat 11

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

}
Run Code Online (Sandbox Code Playgroud)

并且侦听器片段应如下所示:

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. 
            }
        }
    });
}
}
Run Code Online (Sandbox Code Playgroud)

最后,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);
   }
}
Run Code Online (Sandbox Code Playgroud)

值得一提的是updater片段可以是任何形式的片段(不仅仅是DialogFragment),对于使用这些体系结构组件,您应该在app build.gradle文件中包含这些代码行.资源

dependencies {
  def lifecycle_version = "1.1.1"
  implementation "android.arch.lifecycle:extensions:$lifecycle_version"
}
Run Code Online (Sandbox Code Playgroud)


Ale*_*lex 6

我实现了类似于你想要的东西,我的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

枚举:

public class Enums {
    public enum state {
        Master,
        Detail
    }
}
Run Code Online (Sandbox Code Playgroud)

测试活动:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)


Qua*_*yen 5

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

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

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

  • 现在,两个片段之间的逻辑和数据在ViewModel中进行布局。
  • 两个Fragment从ViewModel获取数据/状态,因此它们不需要彼此了解。
  • 此外,借助LiveData的强大功能,您可以基于主Fragment的更改来更改详细信息Fragment,而无需使用先前的回调方法,而采用响应方式。

现在,您完全摆脱了与活动和相关片段紧密耦合的回调。
我强烈建议您通过Google的代码实验室。在步骤5中,您可以找到一个很好的示例。