从 LiveData 观察者调用时,导航组件默认后退堆栈不起作用
Navigation Component default backstack not working when called from a LiveData observer
我正在使用带有导航抽屉的 Android 导航组件(如在 Android Studio 模板中)。我有片段 A、B、C 作为导航抽屉中使用的顶级片段,片段 Z 与导航图中的片段 A 连接。现在我在片段 A 中有一个按钮。单击该按钮将使用安全参数打开片段 Z。
binding.button.setOnClickListener {
val action = NewsFragmentDirections.actionNavNewsToNewsDetailsFragment()
it.findNavController().navigate(action)
}
当片段 Z 打开时,应用栏图标会自动变为后退按钮,这样我就可以返回到片段 A。
这些工作正常,但问题是,当我在实时数据观察器中使用相同的安全参数代码时,后退按钮不起作用。
viewModel.actionNewsDetails.observe(viewLifecycleOwner, {
val action = NewsFragmentDirections.actionNavNewsToNewsDetailsFragment()
findNavController().navigate(action)
})
这里有一些额外的细节
- 一旦我们进入片段 Z,它会像往常一样显示返回导航,但只是点击它不会执行任何操作
- 当我快速点击后退按钮几次时,我注意到应用栏标题闪烁(在片段 A 和 Z 之间变化)
- 当我在片段 Z 中时,我可以通过滑动打开导航抽屉
- 我的直播数据代码写在片段A的onCreateView()
- 实时数据由 ViewModel 中的函数触发
我一直在为这个问题苦苦挣扎。抱歉我的英语不好。
接下来的信息很重要:
When I clicked the back button fast for several time, I noticed the app bar title flickering (changing between fragment A and Z)
我很确定发生了什么,你在片段 Z 中的后退按钮工作正常,你的片段 A 显示并且它的 liveData 再次被触发并再次导航到片段 Z。这发生得非常快,但正如你所指出的,当你做得非常快时,你会看到延迟。
解决方案:在您的 LiveData 观察器中导航到片段 Z 之前,更改 liveData 的值,这样当您返回到片段 A 时它就不会再次触发。
几周前这个问题让我损失了一个小时。
编辑 26/10/2020:
要解决该问题,请实施 SingleLiveEvent
class 并使用它代替 MutableLiveData
。
SingleLiveEvent.class
/**
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
* navigation and Snackbar messages.
* <p>
* This avoids a common problem with events: on configuration change (like rotation) an update
* can be emitted if the observer is active. This LiveData only calls the observable if there's an
* explicit call to setValue() or call().
* <p>
* Note that only one observer is going to be notified of changes.
*/
public class SingleLiveEvent<T> extends MutableLiveData<T> {
private static final String TAG = "SingleLiveEvent";
private final AtomicBoolean mPending = new AtomicBoolean(false);
@MainThread
public void observe(LifecycleOwner owner, final Observer<? super T> observer) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");
}
// Observe the internal MutableLiveData
super.observe(owner, new Observer<T>() {
@Override
public void onChanged(@Nullable T t) {
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t);
}
}
});
}
@MainThread
public void setValue(@Nullable T t) {
mPending.set(true);
super.setValue(t);
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
public void call() {
setValue(null);
}
}
Kotlin 版本
class SingleLiveEvent<T> : MutableLiveData<T>() {
val TAG: String = "SingleLiveEvent"
private val mPending = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG,"Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, Observer<T> { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
@MainThread
override fun setValue(@Nullable t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
fun call() {
value = null
}
}
我正在使用带有导航抽屉的 Android 导航组件(如在 Android Studio 模板中)。我有片段 A、B、C 作为导航抽屉中使用的顶级片段,片段 Z 与导航图中的片段 A 连接。现在我在片段 A 中有一个按钮。单击该按钮将使用安全参数打开片段 Z。
binding.button.setOnClickListener {
val action = NewsFragmentDirections.actionNavNewsToNewsDetailsFragment()
it.findNavController().navigate(action)
}
当片段 Z 打开时,应用栏图标会自动变为后退按钮,这样我就可以返回到片段 A。
这些工作正常,但问题是,当我在实时数据观察器中使用相同的安全参数代码时,后退按钮不起作用。
viewModel.actionNewsDetails.observe(viewLifecycleOwner, {
val action = NewsFragmentDirections.actionNavNewsToNewsDetailsFragment()
findNavController().navigate(action)
})
这里有一些额外的细节
- 一旦我们进入片段 Z,它会像往常一样显示返回导航,但只是点击它不会执行任何操作
- 当我快速点击后退按钮几次时,我注意到应用栏标题闪烁(在片段 A 和 Z 之间变化)
- 当我在片段 Z 中时,我可以通过滑动打开导航抽屉
- 我的直播数据代码写在片段A的onCreateView()
- 实时数据由 ViewModel 中的函数触发
我一直在为这个问题苦苦挣扎。抱歉我的英语不好。
接下来的信息很重要:
When I clicked the back button fast for several time, I noticed the app bar title flickering (changing between fragment A and Z)
我很确定发生了什么,你在片段 Z 中的后退按钮工作正常,你的片段 A 显示并且它的 liveData 再次被触发并再次导航到片段 Z。这发生得非常快,但正如你所指出的,当你做得非常快时,你会看到延迟。
解决方案:在您的 LiveData 观察器中导航到片段 Z 之前,更改 liveData 的值,这样当您返回到片段 A 时它就不会再次触发。
几周前这个问题让我损失了一个小时。
编辑 26/10/2020:
要解决该问题,请实施 SingleLiveEvent
class 并使用它代替 MutableLiveData
。
SingleLiveEvent.class
/**
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
* navigation and Snackbar messages.
* <p>
* This avoids a common problem with events: on configuration change (like rotation) an update
* can be emitted if the observer is active. This LiveData only calls the observable if there's an
* explicit call to setValue() or call().
* <p>
* Note that only one observer is going to be notified of changes.
*/
public class SingleLiveEvent<T> extends MutableLiveData<T> {
private static final String TAG = "SingleLiveEvent";
private final AtomicBoolean mPending = new AtomicBoolean(false);
@MainThread
public void observe(LifecycleOwner owner, final Observer<? super T> observer) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");
}
// Observe the internal MutableLiveData
super.observe(owner, new Observer<T>() {
@Override
public void onChanged(@Nullable T t) {
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t);
}
}
});
}
@MainThread
public void setValue(@Nullable T t) {
mPending.set(true);
super.setValue(t);
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
public void call() {
setValue(null);
}
}
Kotlin 版本
class SingleLiveEvent<T> : MutableLiveData<T>() {
val TAG: String = "SingleLiveEvent"
private val mPending = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG,"Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, Observer<T> { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
@MainThread
override fun setValue(@Nullable t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
fun call() {
value = null
}
}