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

时间:2018-02-21 19:51:02

标签: android android-livedata

我正在使用Android架构组件。 我想要的是当用户在Edittext中键入“0”并单击按钮以使用新的替换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();
}

Reopistory类:

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

12 个答案:

答案 0 :(得分:10)

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

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

更新: 在支持lib v28中,名为ViewLifeCycleOwner的新LifeCycleOwner应修复here

中的更多信息

答案 1 :(得分:6)

以下是我如何解决此问题的示例。 [测试并工作]

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

        }
    });

答案 2 :(得分:5)

不应使用getActivity作为LifecycleOwner,而应使用fragment。

更改

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

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

答案 3 :(得分:2)

您不应该在viewmModel中创建onCreateView,而应在onCreate中创建,这样每次创建视图时都不会为数据添加监听器。< / p>

答案 4 :(得分:2)

这是你在做错什么...

  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()”。

因为当您在obtain方法中传递getActivity()时,每当您打开片段时,您都将用Activity而不是片段附加观察者的新实例。因此,即使您杀死片段,观察者也将保持生命。 因此,当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方法中的一种条件来停止此操作。

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

        }

    })

从我的研究中发现,如果使用与其相应活动的ViewModel进行片段化,  因此,即使您开始观察实时数据,它也将首先向您发送最近发射的项目。即使您没有从片段中调用它。

所以onChange方法被调用了两次

  1. 当片段处于​​开始状态时-接收最近发出的物品

  2. 当片段处于​​恢复状态时-接收片段对api的调用。

所以在更改后,我总是像这样通过viewLifecycleOwner检查片段的状态

   if(viewLifecycleOwner.lifecycle.currentState==Lifecycle.State.RESUMED){
      // if the fragment in resumed state then only start observing data
        }
Fragments和Activity都提供了

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

在Java中,您可以使用viewlifecycleowner的getViewLifecycleOwner()接口。

答案 5 :(得分:1)

要清楚,要实例化一个ViewModel,我们要么传递Fragment本身的上下文(将这个ViewModel限定在其中),要么传递将这个片段作为上下文的活动。

如果我们这样做的话,它们是不一样的,它们有不同的用途

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

我们将viewmodel的这个实例附加到片段的父活动上,在该片段死亡后,该viewmodel的实例将保留在内存中,以保留对父活动的引用。

如果我们这样做

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

我们将viewmodel实例的作用域放在父对象本身中,因此,只要片段死亡,该viewmodel实例也会被删除。

现在,观察者是相同的,我们需要指定我们只希望观察Fragment的生存期,例如,我们要观察直到该片段被破坏,如果不是,则分离任何观察者在此片段中,为此,viewLyfeCycleOwner到来,它将一直观察到该片段死亡或暂停以进入另一个片段,在每个片段中使用它很重要:

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

如果我们将此观察者附加到活动上,

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

它将一直进行观察,直到持有该片段的父活动消失为止,这不是一个好主意,因为它将使多个观察者订阅同一活动数据。

答案 6 :(得分:0)

viewmModel = ViewModelProviders.of(getActivity(), viewModelFactory)
            .get(VModel.class);

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

答案 7 :(得分:0)

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

tasks:
  - debug:
      msg: "{{ item.properties.ipConfigurations|
               json_query('[].properties.privateIPAddress') }}"
    loop: "{{ ansible_facts.azure_networkinterfaces }}"

答案 8 :(得分:0)

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

即与科特琳:

.navbar-nav > li > a {
    font-size: 20px;
}
.nav { right: 10px; }

答案 9 :(得分:0)

我遇到了同样的问题,当我创建一个片段并向onCreate()onCreateView()声明一个观察者时,旋转屏幕后,我的livedata运行了两次。 为了解决此问题,我尝试了kotlin扩展,以便在创建另一个观察器之前先删除观察者,然后尝试将其移除到onDestroyView()中,并尝试在viewmodel声明({{1} }),但所有测试均失败。

但是最终我找到了使用协程的解决方案。我不知道这是否是个好习惯,但是可以用:

requiredActivity - this - viewLifecycleOwner

答案 10 :(得分:0)

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)

答案 11 :(得分:-1)

在@ 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时执行某项操作,并且只会调用一次。