【问题标题】:ViewModel onchange gets called multiple times when back from Fragment从 Fragment 返回时,ViewModel onchange 被多次调用
【发布时间】:2020-08-16 15:28:20
【问题描述】:

我正在使用 Android 架构组件。 我想要的是当用户在 Edittext 中键入“0”并单击 Button 以用新片段替换 Fragment 时,如果键入任何其他内容,则发布 Toast 错误消息。问题是当我从新的Fragment(BlankFragment) 返回并再次单击按钮并再次键入“0”并单击时,onchange() 被多次调用,因此片段被多次创建

FragmentExample.class:

     @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        manager = getActivity().getSupportFragmentManager();
        viewmModel = ViewModelProviders.of(getActivity(), viewModelFactory)
                .get(VModel.class);

        View v = inflater.inflate(R.layout.fragment_list, container, false);   
        b = (Button) v.findViewById(R.id.b);
        et = (EditText) v.findViewById(R.id.et);

        viewmModel.observeData().observe(getActivity(), new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {

                if(s.equals("0")) {
                    BlankFragment fragment = (BlankFragment) manager.findFragmentByTag(DETAIL_FRAG);
                    if (fragment == null) {
                        fragment = BlankFragment.newInstance();
                    }
                    addFragmentToActivity(manager,
                            fragment,
                            R.id.root_activity_detail,
                            DETAIL_FRAG
                    );
                } else {
                    Toast.makeText(getContext(), "Wrong text", Toast.LENGTH_SHORT).show();
                }
            }
        });

        b.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                viewmModel.setData(et.getText().toString());
            }
        });
        return v;
    }
    private void addFragmentToActivity(FragmentManager fragmentManager, BlankFragment fragment, int root_activity_detail, String detailFrag) {
        android.support.v4.app.FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(root_activity_detail, fragment, detailFrag).addToBackStack(detailFrag);
        transaction.commit();
    }

存储库类:


    public class Repository {
    MutableLiveData<String> dataLive = new MutableLiveData<>();  

    public Repository() {

    }

    public void setListData(String data) {
       dataLive.setValue(data);
    }

    public MutableLiveData<String> getData() {
        return dataLive;
    }
}

BlankFragment.class:

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        listItemViewModel = ViewModelProviders.of(this, viewModelFactory)
                .get(VModel.class);
        listItemViewModel.setData("");
        return inflater.inflate(R.layout.fragment_blank, container, false);
    }

【问题讨论】:

    标签: android android-livedata


    【解决方案1】:

    要将观察者添加到 LiveData,您应该在 onViewCreated 中初始化观察者(就像您所做的那样)并附加 viewLifecycleOwner。所以,它应该是这样的:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            viewModel.yourVariable.observe(viewLifecycleOwner, Observer{ newValue ->
            // todo your function
    })
    

    更多信息请见Kotlin Android Training Livedata

    【讨论】:

      【解决方案2】:

      Flow 类似问题的解决方案

      如果您使用 Flow 而不是 LiveData,请不要忘记使用 viewLifecycleOwner.lifecycleScope.launch 而不是 生命周期范围.launch

      viewLifecycleOwner.lifecycleScope.launch {
              flow.flowOn(Default).collect {
                  requireContext()
              }
          }
      

      或带扩展名:

      扩展名:

      fun <T> Flow<T>.launchWhenStarted(lifecycleOwner: LifecycleOwner) {
          lifecycleOwner.lifecycleScope.launchWhenStarted {
              this@launchWhenStarted.collect()
          }
      }
      

      在片段的 onViewCreated 中:

      availableLanguagesFlow
          .onEach {
              //update view
          }.launchWhenStarted(viewLifecycleOwner)
      

      【讨论】:

        【解决方案3】:

        您应该使用片段,而不是使用 getActivity 作为 LifecycleOwner。

        改变

        viewModel.observeData().observe(getActivity(), new Observer<String>() {
        

        viewModel.observeData().removeObservers(this);
        viewModel.observeData().observe(this, new Observer<String>() {
        

        【讨论】:

        • 当我们使用observe(getActivity())时,我们将ViewModel从活动共享到片段,但如果仅使用this,则意味着我们注册了一个新所有者-Fragment,并且不再可以访问活动@987654328 @
        【解决方案4】:

        我遇到了同样的问题,当我创建一个片段并将观察者声明为onCreate()onCreateView() 时,屏幕旋转后,我的livedata 运行了两次。 为了解决这个问题,我尝试了kotlin扩展,以便在创建另一个观察者之前删除观察者,我尝试删除到onDestroyView(),我尝试在viewmodel声明(requiredActivity - this - viewLifecycleOwner)时更改生命周期所有者但所有测试失败。

        但最后我找到了协程的解决方案。我不知道这是否是一个好习惯,但它有效:

        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        
          // ......
        
            // observer
            CoroutineScope(Dispatchers.Main).launch {
                delay(100)
                mMainViewModel.getMailings().observe(viewLifecycleOwner, Observer { showViewModelMailingsList(it) })
            }
        
            // call
            mMainViewModel.responseMailingList(preferences.userEmailAccount, preferences.deviceToken)
        
           // ....
        
        }
        

        【讨论】:

          【解决方案5】:

          明确地说,要实例化一个 ViewModel,我们要么传递 Fragment 本身的上下文,这个 viewmodel 将在其中作用域,或者我们传递持有这个片段的 Activity 作为上下文。

          不一样,他们有不同的目的,如果我们这样做

          listItemViewModel = ViewModelProvider(requireActivity(), viewModelFactory)
                          .get(VModel.class);
          

          我们将这个viewmodel的实例附加到fragment的父activity,在这个fragment dies 之后,那个viewmodel的实例将被保存在内存中,持有对父activity的引用。

          如果我们这样做

          // requireContext() or this
          listItemViewModel = ViewModelProvider(requireContext(), viewModelFactory)
                          .get(VModel.class);
          

          我们将视图模型的实例限定在父级本身中,因此每当片段死亡时,该视图模型实例也会被删除。

          现在,观察者是一样的,我们需要指定我们只想观察到片段的生命周期,例如,我们想观察到这个片段被销毁,然后分离任何观察者,如果我们不是在这个片段中,为此,它出现了viewLyfeCycleOwner,它将一直观察到该片段死亡或暂停到另一个片段,在每个片段中使用它很重要:

           viewmModel.observeData().observe(viewLyfeCycleOwner, new Observer<String>() { ... }
          

          如果我们将这个观察者附加到活动中

          viewmModel.observeData().observe(getActivity(), new Observer<String>() { ... }
          

          它会一直观察直到持有片段的父 Activity 死亡,这不是一个好主意,因为它会为多个观察者订阅相同的实时数据。

          【讨论】:

            【解决方案6】:

            在@Samuel-Eminet 之后添加一些有用的信息, 确实,onCreate(Bundle?)Fragment 创建时仅调用一次,当您按下返回时,将重新创建视图而不是片段(因此为什么 ViewModel 是相同的。如果您订阅生命周期的任何方法这会影响视图,它将一次又一次地重新订阅。 观察者已经走了,即使您要求liveData.hasObservers(),您也无法分辨。

            最好的办法是在onCreate(Bundle?) 时订阅,但我们中的许多人都在使用binding,而view 目前还没有创建,所以这是最好的方法:

            override fun onCreate(savedInstanceState: Bundle?) {
                super.onCreate(savedInstanceState)
                lifecycleScope.launchWhenStarted {
                    subscribeUI()
                }
            }
            

            现在您告诉Fragment 的生命周期在它启动Fragment 时执行某些操作,并且它只会调用一次。

            【讨论】:

            • 投反对票的人,很高兴您能评论为什么这对改进它不起作用。干杯!
            【解决方案7】:

            这是我如何解决此问题的示例。[测试和工作]

             viewModel.getLoginResponse().observe(getViewLifecycleOwner(), new Observer<String>() {
                    @Override
                    public void onChanged(String response) {
                        if(getViewLifecycleOwner().getLifecycle().getCurrentState()== Lifecycle.State.RESUMED){
                            // your code here ...
                        }
            
                    }
                });
            

            【讨论】:

            • 我什至不想知道为什么会这样,只要修复它,谢谢
            • 赞......
            【解决方案8】:

            这就是你做错了什么......

              viewmModel.observeData().observe(getActivity(), new Observer<String>() {
                @Override
                public void onChanged(@Nullable String s) {
            
                    if(s.equals("0")) {
                        BlankFragment fragment = (BlankFragment) manager.findFragmentByTag(DETAIL_FRAG);
                        if (fragment == null) {
                            fragment = BlankFragment.newInstance();
                        }
                        addFragmentToActivity(manager,
                                fragment,
                                R.id.root_activity_detail,
                                DETAIL_FRAG
                        );
                    } else {
                        Toast.makeText(getContext(), "Wrong text", Toast.LENGTH_SHORT).show();
                    }
                }
            });
            

            在上面的代码中,您可以使用“this”或“viewLifecycleOwner”来代替“getActivity()”。

            因为当您在观察方法中传递 getActivity() 时,每当您打开片段时,您都会将观察者的新实例附加到 Activity 而不是片段中。所以即使你杀死了你的片段,观察者也会保持活力。 所以当 livedata postvalue 时,它​​会向所有的观察者发送数据, 由于观察 livedata 的观察者太多,所以都会得到通知。 因此,您的观察者被调用了太多次。 所以你必须观察像这样的片段中的实时数据。

              viewmModel.observeData().observe(this, new Observer<String>() {
                @Override
                public void onChanged(@Nullable String s) {
            
                    if(s.equals("0")) {
                        BlankFragment fragment = (BlankFragment) manager.findFragmentByTag(DETAIL_FRAG);
                        if (fragment == null) {
                            fragment = BlankFragment.newInstance();
                        }
                        addFragmentToActivity(manager,
                                fragment,
                                R.id.root_activity_detail,
                                DETAIL_FRAG
                        );
                    } else {
                        Toast.makeText(getContext(), "Wrong text", Toast.LENGTH_SHORT).show();
                    }
                }
            });  
            

            但是你的 onchanged 方法仍然会被调用两次。
            您可以通过检查 onchanged 方法中的一个条件来停止此操作。

                dash_viewModel.getDashLiveData().observe(viewLifecycleOwner, object : Observer<AsyncResponse> {
                    override fun onChanged(t: AsyncResponse?) {
                        if(viewLifecycleOwner.lifecycle.currentState==Lifecycle.State.RESUMED){
                            setData(t)
                        }
            
                    }
            
                })
            

            从我的研究中,我发现,如果片段使用其相应活动的 ViewModel, 因此,即使您开始观察 livedata,它也会首先向您发送最近发出的项目。即使您没有从片段中调用它。

            所以 onChange 方法被调用了两次

            1. 当片段处于​​启动状态时 - 接收最近发出的项目

            2. 当 Fragment 处于 Resumed 状态时 - 接收 Fragment 对 api 进行的调用。

            等等改变了,我总是像这样在 viewLifecycleOwner 的帮助下检查片段的状态

               if(viewLifecycleOwner.lifecycle.currentState==Lifecycle.State.RESUMED){
                  // if the fragment in resumed state then only start observing data
                    }
            

            viewlifecycleowner 由 Fragments 和 Activity 提供,因为 Google 直接在支持库 28.0.0 和 androidx 中使用 getViewLifecycleOwner() 方法实现了此解决方案。 viewlifecycleowner 包含有关组件生命周期的信息。

            在 java 中,您可以使用 getViewLifecycleOwner() 而不是 viewlifecycleowner 。

            【讨论】:

              【解决方案9】:

              只需将您的 Observer 声明为一个字段变量,这样您就不会在每次生命周期调用您的代码的那部分时都创建一个新的观察者。 ;)

              即与科特林:

              YourFragment: Fragment() {
              
              private val dataObserver = Observer<Data> { data ->
                    manageData(data)
                }
              
              ...
              
              //now you should subscribe your data after you instantiate your viewModel either in onCreate, onCreateView, onViewCreated, depends on your case..
              
              override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
                  super.onViewCreated(view, savedInstanceState)
                  viewModel.liveData.observe(this, dataObserver)
              }
              
              ...
              
              }
              

              【讨论】:

              • 我也有类似的情况。你能举个例子让我朝着正确的方向前进吗?
              • 刚刚添加了一个简单的例子
              • 不错。我正在使用 Java...我认为 Java 示例会相似吗?基本上在 ViewModel 中的 Observer 对象上使用 observe()?
              • 为了简单起见,您应该尝试在 Fragment、Activity 或 LifeCycleOwner 中使用 observe()。如果您在 ViewModel 类中执行此操作,则必须使用 observeForever 并在 onCleared() 中删除此观察者,因为您永远不应在 ViewModel 类中引用 LifeCycleOwner。希望这很清楚。
              • 我的错,我的意思是使用 observe() 让 Viewmodel 再次观察数据。在上面的示例中,“viewModel.liveData.observe(this, dataObserver)”中的“this”是什么?
              【解决方案10】:

              在片段中只观察一次 livedata。为此调用 onCreate() 中的观察方法,而不是 onCreateView()。当我们按下返回按钮时,会调用 onCreateView() 方法,让 viewmodel 再次观察数据。

               @Override
              public void onCreate(Bundle savedInstanceState) {
                  super.onCreate(savedInstanceState);
              
                 mPatientViewModel.getGetCaseDetailLiveData().observe(this, jsonObjectResponse -> parseViewSentResponse(jsonObjectResponse));
              }
              
              @Override
              public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
                  // TODO: inflate a fragment view
                  View rootView = super.onCreateView(inflater, container, savedInstanceState);
              
                  return rootView;
              }
              

              【讨论】:

                【解决方案11】:

                这里的问题是,当您从活动中分离片段时,片段及其视图模型都不会被破坏。当你回来时,当旧的观察者还在同一个片段中时,你向livedata添加一个新的观察者(如果你在onCreateView()中添加观察者)。 有一个article(实际上甚至是一个 SO 线程)正在谈论它(有解决方案)。

                修复它的简单方法(也在文章中)是在添加观察者之前从实时数据中删除任何观察者。

                更新: 在支持库 v28 中,一个名为 ViewLifeCycleOwner 的新 LifeCycleOwner 应该修复here中的更多信息

                【讨论】:

                • 我不这么认为。ViewModelProviders创建的viewmodel是同一个实例。
                • 使用 viewLifecycleOwner 有同样的问题。当 popBack 到使用发出的最后一个值调用的片段 liveData 时。您应该将观察者放入 onCreate 并将其用作所有者。
                • @djardon 如果您使用的是 ViewBinding,而不是 DataBinding,这是正确的。
                【解决方案12】:
                viewmModel = ViewModelProviders.of(getActivity(), viewModelFactory)
                            .get(VModel.class);
                

                由于你的viewmModelLifecycleOwner是activity,所以只有当生命周期状态为Lifecycle.State.DESTROYED时,观察者才会被自动移除。
                在您的情况下,观察者不会被自动删除。因此您必须手动删除以前的观察者或每次都传递相同的观察者实例。

                【讨论】:

                  【解决方案13】:

                  您不应在onCreateView 中创建viewmModel,而应在onCreate 中创建您的viewmModel,这样您就不会在每次创建视图时都向数据添加监听器。

                  【讨论】:

                  • 这可能是最有效的答案,因为不需要关心或处理每一个LiveData
                  • 如果使用 viewLifecycleOwner 你不能在 onCreate 方法上创建观察者,因为你不能在 onCreateView() 之前或 onDestroyView() 之后调用 viewLifecycleOwner
                  猜你喜欢
                  • 1970-01-01
                  • 2021-07-29
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-10-23
                  • 2014-02-20
                  • 1970-01-01
                  相关资源
                  最近更新 更多