LiveData无法观察到更改

时间:2020-06-12 11:41:49

标签: android kotlin mvvm android-databinding android-livedata

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

ViewModel:

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)

    }
}

BaseViewModel:

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 ?: "")
                    }
                }
            }
        }
    }
}

对话框片段:

class OtpVerificationHelpCodeSentBottomSheetFragment : BaseBottomSheetDialogFragment(){

    private lateinit var rootView: View
    lateinit var binding: BottomSheetFragmentOtpVerificationHelpCodeSentBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

        viewModel = getViewModel<OtpViewModel>()

        binding = DataBindingUtil.inflate(inflater, R.layout.bottom_sheet_fragment_otp_verification_help_code_sent, container, false)

        rootView = binding.root

        return rootView
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)


        val otpViewModel = (viewModel as OtpViewModel)
        binding.viewmodel = otpViewModel

        otpViewModel.resendOTPResponseLiveData.observe(viewLifecycleOwner, Observer {

            it?.let { resendOtpResponse ->
                if(resendOtpResponse.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) }
                }
            }

        })
    }
}

我正在从 DialogFragment 的xml文件中调用viewmodel的 resendOTP(contactId:String)方法:

 <TextView
            android:id="@+id/verification_help_code_sent_resend_code"
            style="@style/TruTextView.SubText2.BottomActions"
            android:layout_height="@dimen/spaceXl"
            android:gravity="center_vertical"
            android:text="@string/verification_help_resend_code"
            android:onClick="@{() -> viewmodel.resendOTP(Integer.toString(viewmodel.userSelectedIndex))}"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/top_guideline" />

现在,每当我尝试从Fragment调用resendOTPResponseLiveData时,它都不会被调用:

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

    }

那么我在这里做什么错了。

编辑

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

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

private val selected = MutableLiveData<LogonModel>()

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

    fun getSelected(): LiveData<LogonModel> {
        return selected
    }

在DialogFragment中:

 otpViewModel.resendOTPResponseLiveData.observe(viewLifecycleOwner, Observer{

           otpViewModel.select(it);

        })

在我要读取值的片段中:

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源:

viewModel = getViewModel<OtpViewModel>()

2 个答案:

答案 0 :(得分:0)

问题在于您实际上并没有在 Fragment Dialog 之间共享 ViewModel 。要共享 ViewModel 的实例,必须从相同的sorted categories: [['baby', 'boy', 'child', 'daughter', 'girl', 'king', 'man', 'queen', 'son', 'woman']] [[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.] [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.] [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]] 中检索它们。

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

如果是这种情况,请注意,在 Koin 中,ViewModelStore Fragment 自己的< em> ViewModelStore 。因此,您正在从自己的 ViewModelStore 中获取 DialogFragment 中的 ViewModel 。另一方面,在您的 Fragment 中,您正在使用getViewModel对其进行检索,您可以在其中指定应检索 ViewModel <的 ViewModelStore 。 / em>来自。因此,您正在从两个不同的 ViewModelStores 中检索 ViewModel ,因此,将获得两个不同的 ViewModel 。与其中一个进行交互不会影响另一个,因为它们不是同一实例。

要解决此问题,您应该从同一 ViewModelStore Fragment DialogFragment 中都检索 ViewModel 。例如,您可以在两者中都使用getSharedViewModel,或者在每个位置手动指定相同的 ViewModelStore ,甚至甚至不指定哪个 Koin 将默认设置为活动的一项。

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

答案 1 :(得分:0)

几个月前,当我重新认识Jetpack库和Kotlin时,如果我对您的理解正确,就会遇到类似的问题。

我认为这里的问题是您正在使用by 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())
}

请注意,我的viewModel类型的AddPatientViewModel仅通过viewModel: XXX by viewModels的作用域为当前片段上下文,对该特定ViewModel所做的任何更改等都只会在我的当前片段中传播。 / p>

类型为patientViewModel的{​​{1}}通过PatientViewModel限定于活动上下文。 这意味着,只要两个片段都属于同一个活动,并且您通过patientViewModel: XXX by activityViewModels获得了ViewModel,则您应该能够在全局范围内观察对ViewModel所做的任何更改(全局意味着同一活动中的任何片段在那里声明)。

请牢记以上所有内容,如果您的viewModel正确地限定在您的活动范围内,并且在两个片段中,您都使用... by activityViewModels检索viewModel并更新了通过by activityViewModels或{{1}观察到的值},您应该能够在同一活动上下文中的任何地方观察到对ViewModel所做的任何更改。

希望如此,这已经很晚了,我在打麻袋之前就看到了这个问题!