问题实现LiveData:观察总是调用时恢复片段(从另一个片段backstack)?



我一步一步学习在我的应用程序中实现LiveData。我的应用程序有一个MainActivity2片段与导航侦听器。ListFragmentDetailListFragment.

我调用函数从服务器上onCreateView获取数据ListFragment通过viewModel,并观察成功后在RecyclerView中填充数据。然后我单击一个项目来显示DetailListFragment中的细节。

问题是当从返回DetailListFragment观察viewModel回忆,但我不想要它

ListFragment

class ListFragment : BaseFragment(), ListClickListener {
private lateinit var _observeListViewModel: Observer<BaseViewModel.State>
lateinit var listViewModel: ListViewModel
private lateinit var adapter: ListAdapter
private var _binding: ListBinding? = null
private val binding get() = _binding!!
override fun onDestroyView() {
super.onDestroyView()
_binding = null
listViewModel.state.removeObserver(_observeListViewModel)
}

private var itemsData = ArrayList<ListResponseDtoListModel>()
@SuppressLint("PrivateResource")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
_binding = ListBinding.inflate(inflater, container, false)

listViewModel =
ViewModelProvider(this).get(ListViewModel::class.java)
adapter = ListAdapter(itemsData, this)
val llm = LinearLayoutManager(requireActivity())
binding.rv.setHasFixedSize(true)
binding.rv.layoutManager = llm
binding.rv.adapter = adapter
//get List
_observeListViewModel =
Observer<BaseViewModel.State> { observeListViewModel(it) }
listViewModel.state.observe(viewLifecycleOwner, _observeListViewModel)
listViewModel.getList(requireContext())
return binding.root
}
private fun observeListViewModel(state: BaseViewModel.State?) {
when (state) {
BaseViewModel.State.Loading -> {
loadingState()
}
is BaseViewModel.State.Error -> {
errorState()
}
is BaseViewModel.State.Success -> {
val data = state.data as ListModel
if (data.status == KopraMobile().SUCCESS) {
if (data.content!!.listResponseDtoList.size == 0) {
nodataState()
} else data.content?.listResponseDtoList.let {
successState(it)
}
} else
errorState()
}
is BaseViewModel.State.SessionTimeout -> {
errorState()
(parentFragment as BaseFragment).logOut()
}
is BaseViewModel.State.ErrorResponse -> {
errorState()
}
else -> {}
}
}
private fun successState(it: Any) {
....
}
private fun loadingState() {
....
}
private fun nodataState() {
....
}
private fun errorState() {
....
}

override fun onItemClicked(dashboardItem: ListResponseDtoListModel?) {
findNavController().navigate(R.id.action_listFragment_to_detailListFragment)
}
}

DetailFragment

class DetailListFragment : BaseFragment(){
private var _binding: DetailListBinding? = null
private val binding get() = _binding!!
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}

private var itemsData = ArrayList<DetailListResponseDtoListModel>()
@SuppressLint("PrivateResource")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
_binding = DetailListBinding.inflate(inflater, container, false)

binding.incToolbar1.header.text = "Detail"
binding.incToolbar1.back.setImageResource(com.google.android.material.R.drawable.material_ic_keyboard_arrow_previous_black_24dp)
binding.incToolbar1.back.setOnClickListener {
findNavController().popBackStack()
}

return binding.root
}
}

ListViewModel

class ListViewModel : BaseViewModel() {
fun getList(context: Context)
{
_state.postValue(State.Loading)
job = CoroutineScope(Dispatchers.IO + exceptionHandler).launch {
try {
val response =
NetworkApi().getListApi().getList( BuildConfig.APPLICATION_ID,  Prefs.getPublicAuthorization(context)
)
withContext(Dispatchers.Main) {
_state.postValue(
if (response.isSuccessful) {
State.Success( response.headers(), response.body()   )
} else {
State.ErrorResponse( response.headers(), response.errorBody()  ) }
)
}
} catch (throwble: Throwable) {
_state.postValue(
State.Error("Error : ${throwble.message.toString()} ")
)
}
}
}
}

BaseViewModel

open class BaseViewModel : ViewModel() {
sealed class State {
object Loading : State()
data class Success(val headers: Headers, val data: Any?) : State()
data class ErrorResponse(val headers: Headers, val errorResponse: ResponseErrorModel) :
State()
data class Error(val message: String?) : State()
data class SessionTimeout(val sessionTimeout: String?) : State()
}
var job: Job? = null
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
_state.postValue(State.Error("Exception handled: ${throwable.localizedMessage}"))
}
val _state = MutableLiveData<State>()
val state: LiveData<State> get() = _state

override fun onCleared() {
super.onCleared()
job?.cancel()
}
}
我希望有人能帮我解决这个问题。谢谢,对不起,我的英语。

你可以使用LiveData的这个子类它只会发出一次

public class SingleLiveEvent<T> extends MutableLiveData<T> {
private static final String TAG = "SingleLiveEvent";
private final AtomicBoolean mPending = new AtomicBoolean(false);
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull 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);
}
}

你面临的问题是这样的:

适当的配置更改如果由于配置更改(如设备旋转)而重新创建活动或片段,它将立即接收最新的可用数据。

  • 我看到过导航或应用状态的改变也会导致同样的行为。
  • 这可能看起来像一个bug,但它实际上是预期的目的

我的建议:

使用广播器/接收器代替观察者。我强烈反对在观察者函数中处理网络状态。最好使用一个可以访问全局Application Context的okhttp拦截器,否则你每次发出另一个请求时都要重写相同的代码。

另外,尽量不要将上下文传递给ViewModels,这个类不需要知道关于视图的任何信息。

最新更新