当前位置: 首页 > 知识库问答 >
问题:

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

东门玺
2023-03-14

在上一次谷歌IO上,谷歌发布了一些新arch组件的预览,其中之一是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
        });
    }
}

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

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

我仍然需要使用将由活动实现的接口,该接口将调用fragmentManager。替换(…),或者有另一种方法可以使用新的架构来实现这一点?

共有3个答案

黄泰宁
2023-03-14

根据谷歌代码实验室(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);
   }

}

侦听器片段应该如下所示:

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

值得一提的是,更新程序片段可以是任何形式的片段(不仅仅是对话片段),为了使用这些架构组件,您应该在应用程序build.gradle文件中包含这些代码行

dependencies {
  def lifecycle_version = "1.1.1"
  implementation "android.arch.lifecycle:extensions:$lifecycle_version"
}
祁辰阳
2023-03-14

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

// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
饶德本
2023-03-14

更新于2017年6月12日,

Android Official提供了一个简单、精确的示例来说明ViewModel如何在主细节模板上工作,您应该先看看它。在片段之间共享数据

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

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

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

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

  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.blank片段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.BlankFragmentB

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

}
 类似资料:
  • 寻找一种方法,使服务在必要时将最新数据提供给订阅的组件。例如:组件 3 将更改写入 API 以下是一些片段和一些描述,说明到目前为止我所做的工作: 该服务通过HTTP从API获取数据。组件1和组件2订阅了服务返回的observable:

  • 问题内容: 我正在开发使用Meteor和React作为视图引擎的应用程序 考虑下图: 从另一个示例反应隐藏组件 当触发C4按钮单击事件时,我需要更改C2组件状态。由于他们没有直接关系,因此我无法直接从C4进入C2状态。 另一个示例是从Component提交表单并获取在另一个Component中声明的数据(输入字段的值)。 我知道可以通过一些技巧来解决此问题(例如,流星会话,通过每个组件传递数据,基

  • 我正在使用angular2开发一个应用程序。我有一个场景,需要在路由时(使用router.navigate())将复杂的数据(对象数组)从一个组件传递到另一个组件(它们不是父子关系,而是两个独立的组件)。我在谷歌上搜索过,大多数结果描述了父子组件的场景。我浏览了结果,找到了这些传递数据的方法。 1)创建共享服务2)通过路由参数 第二种方法适合我(尽管我不喜欢上面解释的复杂数据)。我无法使用共享服务

  • 上下文:我有一个类,看起来像这样: 还有两个s,在其中我将使用和在第一个片段中显示用户名列表,后者显示第二个片段中用户的设备位置标记。我在这两个片段之间使用共享的,以提供所需的数据。如下所示: 在我的资源库课上, 我有一个名为的方法,可以从firestore数据库中实际获取用户。我还创建了一个如下所示的接口回调 这样以后我就可以在我的两个片段中访问获取的用户。在我的第一个片段中, 我调用了,访问了

  • 在Angular 1.x.x中,您只需请求相同的服务,并以相同的实例结束,这使得在服务中共享数据成为可能。 现在在Angular 2中,我有了一个引用我的服务的组件。我可以读取和修改服务中的数据,这很好。当我试图在另一个组件中注入相同的服务时,似乎得到了一个新的实例。 我做错了什么?是模式本身有问题(使用服务共享数据)还是我需要将服务标记为单例(在应用程序的一个实例中)或其他? 我在 btw上 我

  • 我有一个包含两个片段的FragmentActivity(support-v4)。并且都包含一个带有ActionBar的SearchView的菜单。 PlayListFragment: 附注。 我找到了原因,但没有解决方法: 呼叫 如何禁用ActionBar/SearchView/TextView的保存和还原?