簡體   English   中英

問題實施 LiveData:觀察總是在恢復片段時調用(從另一個片段返回堆棧)?

[英]Problem Implement LiveData : Observe always called when resume fragment (backstack from another fragment)?

我正在學習如何在我的應用程序中實施 LiveData。 我的應用程序有一個MainActivity ,其中有 2 個帶有導航監聽器的片段。 ListFragmentDetailListFragment

我調用 function 通過 viewModel 在 onCreateView ListFragment上從服務器獲取數據,並在成功時觀察它以在 RecyclerView 中填充數據。 然后我單擊一項以在 DetailListFragment 中顯示詳細信息。

問題是從DetailListFragment回來時,重新調用了observe viewModel但我不想要它

吼叫我的代碼

列表片段

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

}

細節片段

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

列表視圖模型

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

基礎視圖模型

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

你面臨的問題是這個:

“適當的配置更改如果由於配置更改(如設備輪換)而重新創建活動或片段,它會立即接收最新的可用數據。”

  • 我已經看到導航或應用程序 state 更改也會導致相同的行為。
  • 它可能看起來像一個錯誤,但它實際上是預期的目的。

我的建議:

使用 Broadcaster/Receiver 而不是觀察者。 我強烈反對在您的觀察者 function 處處理網絡狀態。最好使用可以訪問全局應用程序上下文的 okhttp 攔截器,否則每次發出另一個請求時都會重新編寫相同的代碼。

另外,盡量不要將上下文傳遞給 ViewModel,此類不需要了解有關視圖的任何信息。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM