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

新手上路,请多包涵

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

文档 中,谷歌显示了该组件的一种可能用途:

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

这个常见的痛点可以通过使用 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
        });
    }
}

我对不需要那些用于片段的接口通过活动进行通信的可能性感到非常兴奋。

但谷歌的例子并没有准确地显示我将如何从主控中调用细节片段。

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

原文由 alexpfx 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 587
2 个回答

于 2017 年 6 月 12 日更新,

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

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

我试图实现这一点并为此做了一个简单的项目。你可以看看它。大部分代码引用自 Google IO 2017,结构也是如此。 https://github.com/charlesng/SampleAppArch

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

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

  1. 您想在中间人中发送和接收的内容,它们应该只在 View Model 中发送和接收
  2. 在片段类中修改似乎并没有太多。因为它只是将实现从“接口回调”更改为“监听和响应 ViewModel”
  3. 视图模型初始化似乎很重要,并且可能在活动中被调用。
  4. 使用 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 (它应该有一个更好的名字而不是这个)

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

3.空白片段A

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

}

4.空白片段B

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

}

原文由 Long Ranger 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

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

原文由 Daniel 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题