从 Fragment 返回时,ViewModel onchange 被多次调用

ViewModel onchange gets called multiple times when back from Fragment

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

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

存储库class:


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

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

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

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

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

中的更多信息

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

改变

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

viewModel.observeData().removeObservers(this);
viewModel.observeData().observe(this, new Observer<String>() {
viewmModel = ViewModelProviders.of(getActivity(), viewModelFactory)
            .get(VModel.class);

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

在一个片段中只观察一次livedata。为此调用 onCreate() 中的 observe 方法而不是 onCreateView()。当我们按下后退按钮时,调用 onCreateView() 方法,使视图模型再次观察数据。

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

只需将您的 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)
}

...

}

这是你做错的地方...

  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();
        }
    }
});
在上面的代码中

而不是 "getActivity()" 您可以使用 "this" 或 "viewLifecycleOwner".

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

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

        }

    })

根据我的研究,我发现,如果片段使用其对应 activity 的 ViewModel, 因此,即使您开始观察实时数据,它也会首先向您发送最近发出的项目。即使你没有从你的片段中调用它。

所以 onChange 方法被调用了两次

  1. 当片段处于​​开始状态时 - 接收最近发射的项目

  2. 当片段处于​​恢复状态时 - 接收片段对 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 。

这是我如何解决此问题的示例。[已测试并正在运行]

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

        }
    });

在@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 时做一些事情,并且它只会调用它一次。

明确地说,为了实例化 ViewModel,我们传递片段本身的上下文(此视图模型将在其中限定范围),或者我们传递包含此片段的 activity 作为上下文。

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

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

我们将视图模型的这个实例附加到片段的父级 activity,在该片段死亡后,该视图模型的实例将保留在内存中并持有对父级 activity 的引用.

如果我们这样做

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

我们将 viewmodel 实例的范围限定在父级本身中,因此每当 fragment 终止时,该 viewmodel 实例也会被删除。

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

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

如果我们用

将这个观察者附加到activity
viewmModel.observeData().observe(getActivity(), new Observer<String>() { ... }

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

我遇到了同样的问题,当我创建一个片段并将观察者声明到 onCreate()onCreateView() 时,在屏幕旋转后,我的 livedata 运行了两次。 为了解决这个问题,我尝试了 kotlin 扩展,以便在创建另一个观察者之前删除观察者,我试图将其删除到 onDestroyView(),我试图在视图模型声明时更改 lifecyclecleOwner (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)

   // ....

}

Flow 类似问题的解决方案

如果您使用 Flow 而不是 LiveData,那么不要忘记使用 viewLifecycleOwner.lifecycleScope.launch 而不是 lifecycleScope.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)

要将观察者添加到 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