Android LiveData无法观察到更改

Android LiveData无法观察到更改,android,kotlin,mvvm,android-databinding,android-livedata,Android,Kotlin,Mvvm,Android Databinding,Android Livedata,我正在更新ViewModel中DialogFragment中的LiveData值,但无法获取Fragment中的值 视图模型: class OtpViewModel(private val otpUseCase: OtpUseCase, analyticsModel: IAnalyticsModel) : BaseViewModel(analyticsModel) { override val globalNavModel = GlobalNavModel(titleId = R.str

我正在更新ViewModel中DialogFragment中的LiveData值,但无法获取Fragment中的值

视图模型:

class OtpViewModel(private val otpUseCase: OtpUseCase, analyticsModel: IAnalyticsModel) : BaseViewModel(analyticsModel) {
    override val globalNavModel = GlobalNavModel(titleId = R.string.otp_contact_title, hasGlobalNavBar = false)

    private val _contactListLiveData = MutableLiveData<List<Contact>>()
    val contactListLiveData: LiveData<List<Contact>>
        get() = _contactListLiveData

    private lateinit var cachedContactList: LiveData<List<Contact>>
    private val contactListObserver = Observer<List<Contact>> {
        _contactListLiveData.value = it
    }



    private lateinit var cachedResendOtpResponse: LiveData<LogonModel>
    private val resendOTPResponseObserver = Observer<LogonModel> {
        _resendOTPResponse.value = it
    }

    private var _resendOTPResponse = MutableLiveData<LogonModel>()
    val resendOTPResponseLiveData: LiveData<LogonModel>
        get() = _resendOTPResponse

    var userSelectedIndex : Int = 0 //First otp contact selected by default

    val selectedContact : LiveData<Contact>
        get() = MutableLiveData(contactListLiveData.value?.get(userSelectedIndex))

    override fun onCleared() {
        if (::cachedContactList.isInitialized) {
            cachedContactList.removeObserver(contactListObserver)
        }

        if (::cachedOtpResponse.isInitialized) {
            cachedOtpResponse.removeObserver(otpResponseObserver)
        }

        super.onCleared()
    }

    fun updateIndex(pos: Int){
        userSelectedIndex = pos
    }

    fun onChangeDeliveryMethod() {
        navigate(
            OtpVerificationHelpCodeSentBottomSheetFragmentDirections
                .actionOtpContactVerificationBottomSheetToOtpChooseContactFragment()
        )
    }

    fun onClickContactCancel() {
        navigateBackTo(R.id.logonFragment, true)
    }

    fun retrieveContactList() {
        cachedContactList = otpUseCase.fetchContactList()
        cachedContactList.observeForever(contactListObserver)
    }



    fun resendOTP(contactId : String){
        navigateBack()
        cachedResendOtpResponse = otpUseCase.resendOTP(contactId)
        cachedResendOtpResponse.observeForever(resendOTPResponseObserver)

    }
}
abstract class BaseViewModel(val analyticsModel: IAnalyticsModel) : ViewModel() {
    protected val _navigationCommands: SingleLiveEvent<NavigationCommand> = SingleLiveEvent()
    val navigationCommands: LiveData<NavigationCommand> = _navigationCommands

    abstract val globalNavModel: GlobalNavModel


    /**
     * Posts a navigation event to the navigationsCommands LiveData observable for retrieval by the view
     */
    fun navigate(directions: NavDirections) {
        _navigationCommands.postValue(NavigationCommand.ToDirections(directions))
    }

    fun navigate(destinationId: Int) {
        _navigationCommands.postValue(NavigationCommand.ToDestinationId(destinationId))
    }

    fun navigateBack() {
        _navigationCommands.postValue(NavigationCommand.Back)
    }

    fun navigateBackTo(destinationId: Int, isInclusive: Boolean) {
        _navigationCommands.postValue(NavigationCommand.BackTo(destinationId, isInclusive))
    }

    open fun init() {
        // DEFAULT IMPLEMENTATION - override to initialize your view model
    }


    /**
     * Called from base fragment when the view has been created.
     */
    fun onViewCreated() {
        analyticsModel.onNewState(getAnalyticsPathCrumb())
    }

    /**
     * gets the Path for the current page to be used for the trackstate call
     *
     * Override this method if you need to modify the path
     *
     * the page id for the track state call will be calculated in the following manner
     * 1) analyticsPageId
     * 2) titleId
     * 3) the page title string
     */
    protected fun getAnalyticsPathCrumb() : AnalyticsBreadCrumb {

        return analyticsBreadCrumb {
            pathElements {
                if (globalNavModel.analyticsPageId != null) {
                    waPath {
                        path = PathElement(globalNavModel.analyticsPageId as Int)
                    }
                } else if (globalNavModel.titleId != null) {
                    waPath {
                        path = PathElement(globalNavModel.titleId as Int)
                    }
                } else {
                    waPath {
                        path = PathElement(globalNavModel.title ?: "")
                    }
                }
            }
        }
    }
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        Log.d("OtpVerify" , "OnViewCreatedCalled")
        viewModel.onViewCreated()
        val otpViewModel = (viewModel as OtpViewModel)

        binding.lifecycleOwner = this
        binding.viewmodel = otpViewModel
        binding.toAuthenticated = OtpVerifyFragmentDirections.actionOtpVerifyFragmentToAuthenticatedActivity()
        binding.toVerificationBtmSheet = OtpVerifyFragmentDirections.actionOtpVerifyFragmentToOtpContactVerificationCodeSentBottomSheet()


        otpViewModel.resendOTPResponseLiveData.observe(viewLifecycleOwner, Observer {
            if(it?.statusCode.equals("000")){
                //valid status code
                requireActivity().toastMessageOtp(getString(R.string.otp_code_verification_sent))
            }else{
                //show the error model
                it?.errorModel?.let { it1 -> handleDiasNetworkError(it1) }
            }
        })

    }
 otpViewModel.resendOTPResponseLiveData.observe(viewLifecycleOwner, Observer{

           otpViewModel.select(it);

        })
所以我在这里做错了什么

编辑

基本上,我需要在dialogfragment中单击Listener(重发按钮单击),并需要在片段中读取它。所以我使用了SharedViewModel的概念

因此,我在ViewModel中进行了必要的更改:

private val selected = MutableLiveData<LogonModel>()

 fun select(logonModel: LogonModel) {
        selected.value = logonModel
    }

    fun getSelected(): LiveData<LogonModel> {
        return selected
    }
在我要读取值的片段中:

otpViewModel.getSelected().observe(viewLifecycleOwner, Observer {

            Log.d("OtpVerify" , "ResendCalled")
            // Update the UI.
            if(it?.statusCode.equals("000")){
                //valid status code
                requireActivity().toastMessageOtp(getString(R.string.otp_code_verification_sent))
            }else{
                //show the error model
                it?.errorModel?.let { it1 -> handleDiasNetworkError(it1) }
            }
        })
但它仍然不起作用

编辑:

片段的ViewModel源:

viewModel = getSharedViewModel<OtpViewModel>(from = {
            Navigation.findNavController(container as View).getViewModelStoreOwner(R.id.two_step_authentication_graph)
        })
viewModel=getSharedViewModel(从={
Navigation.findNavController(容器作为视图).getViewModelStoreOwner(R.id.two\u step\u authentication\u graph)
})
dialogfragment的ViewModel源:

viewModel = getSharedViewModel<OtpViewModel>(from = {
            Navigation.findNavController(container as View).getViewModelStoreOwner(R.id.two_step_authentication_graph)
        })
viewModel = getViewModel<OtpViewModel>()
viewModel=getViewModel()

问题在于您实际上没有在片段和对话框之间共享ViewModel。要共享ViewModel实例,必须从相同的
ViewModelStore
中检索它们

用于检索ViewModels的语法似乎来自第三方框架。我觉得可能

如果是这种情况,请注意,在Koin中,
getViewModel
从片段自己的ViewModelStore检索ViewModel。因此,您正在从自己的ViewModelStore检索DialogFragment中的ViewModel。另一方面,在片段中,您使用
getSharedViewModel
检索它,您可以在其中指定它应该从哪个ViewModelStore检索ViewModel。因此,您将从两个不同的ViewModelStore检索ViewModel,从而获得两个不同的ViewModel。与其中一个交互不会影响另一个,因为它们不是同一个实例

要解决这个问题,您应该从同一个ViewModelStore中检索片段和DialogFragment中的ViewModel。例如,您可以在这两种情况下都使用
getSharedViewModel
,可以在每种情况下手动指定相同的ViewModelStore,甚至不指定哪个Koin将默认为其活动的一个


您甚至可以在片段中使用
getViewModel
,然后将其自己特定的ViewModelStore传递给DialogFragment,在DialogFragment中,您可以使用
getSharedViewModel
,指定传递的片段的ViewModelStore。

对于Jetpack库来说是新的,几个月前我遇到了类似的问题,如果我理解正确的话

我认为这里的问题是,您正在使用viewModels的
检索ViewModel,这意味着您返回的ViewModel的范围将仅限于当前的片段上下文。。。如果您希望在应用程序的多个部分之间共享视图模型,那么它们必须是活动范围的

例如:

//this will only work for the current fragment, using this declaration here and anywhere else and observing changes wont work, the observer will never fire, except if the method is called within the same fragment that this is declared
private val viewModel: AddPatientViewModel by viewModels {
    InjectorUtils.provideAddPatientViewModelFactory(requireContext())
}

//this will work for the ANY fragment in the current activies scope, using this code and observing anywhere else should work, the observer will fire, except if the method is called fro another activity
private val patientViewModel: PatientViewModel by activityViewModels {
    InjectorUtils.providePatientViewModelFactory(requireContext())
}
请注意,我的
AddPatientViewModel
类型的
viewModel
仅通过
viewModel:XXX by viewModels
作用于当前片段上下文,对该特定viewModel所做的任何更改等将仅在我的当前片段中传播

其中,
patientViewModel
类型的as
patientViewModel
通过
patientViewModel:XXX by activityViewModels
作用于活动上下文。 这意味着,只要两个片段属于同一个活动,并且您通过
获取ViewModel。。。通过activityViewModels
,您应该能够在全局范围内观察对ViewModel所做的任何更改(全局是指声明它的同一活动中的任何片段)

如果您的viewModel的范围正确地限定在您的活动中,并且在这两个片段中,您使用activityViewModels的
检索viewModel,并通过
XXX.postValue(YYY)更新观察到的值,请牢记以上所有内容
XXX.value=YYY
您应该能够在同一活动上下文中的任何位置观察对ViewModel所做的任何更改


希望这是有意义的,现在已经很晚了,我在睡觉前看到了这个问题

代码中不清楚是否会在
cachedResendOtpResponse
上设置值,因为我们不知道
OtpUseCase.resendop
的确切功能。我也不明白
cachedResendOtpResponse
的目的是什么。在我看来,你也可以用联系人ID字符串的私有
MutableLiveData
和公共
转换来做同样的事情。switchMap
会对该字符串的更改做出反应,并返回你的
LiveData
。嗨,Michael,OtpUseCase.resendop进行网络调用并发送数据。返回类型为LiveData。为了捕获livedata,我已经这样做了,否则它将返回一个编译错误,expected livedata found mutablelivedata。需要@Michael提供更多详细信息吗?我的观点是,我们无法验证它的正确性,因为我们没有该代码,所以据我们所知,您可能有一个导致此问题的bug。嗨,Michael,livedata可以从dialogfragment中正确地观察值,但对于fragment则失败。因此,我假设OtpUseCase.resendotp中没有问题。我想我已经尝试过了。我会再次检查并让您知道。好的!让我知道。如果它仍然不起作用,你也可以分享一些你的OtpUseCase。我看到您将CachedResponseSendotPresponse分配给调用OtpUseCase的结果,然后永远观察它(在onCleared中停止它),并将ResendotResponseEliveData更新为它的回调。另外,如果您可以分享如何在Koin模块中声明ViewModel,那也将非常好。