我正在使用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);
}
答案 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方法被调用了两次
当片段处于开始状态时-接收最近发出的物品
当片段处于恢复状态时-接收片段对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 而不是 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
时执行某项操作,并且只会调用一次。