Значение LiveData возвращает значение null

#android #kotlin #mvvm #kotlin-coroutines #android-livedata

Вопрос:

Почему мое внешнее неизменяемое значение LiveData возвращает значение null, когда я хочу получить его за пределами области просмотра?

Как вы можете видеть в logcat, нет никаких проблем, когда я хочу получить его в viewModelScope или вызвать функцию входа во второй раз.

Вот моя модель представления

 class LoginViewModel(private val mainRepository: MainRepository) : ViewModel() {

    private val _loginResponse = MutableLiveData<LoginResponse>()

    val loginResponse: LiveData<LoginResponse> = _loginResponse

    fun login(post: LoginBody) {
        viewModelScope.launch {
            try {
                _loginResponse.value = mainRepository.loginPost(post = post)
                Log.d(
                    LOGCAT,
                    "Login response : ${loginResponse.value?.data?.answerCount}"
                )
            } catch (e: Exception) {
                Log.d(LOGCAT, "Login error: $e")
            }
        }
        loginResponseOutsideOfTheViewModelScope()
    }

    fun loginResponseOutsideOfTheViewModelScope() {
        Log.d(LOGCAT, "LoginResponseOutsideOfTheViewModelScope : ${loginResponse.value?.data?.answerCount}")
    }
}

class LoginViewModelFactory(val app: GameApplication) :
    ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        val retrofitService = app.retrofit
        val context = app.applicationContext
        val mainRepository = MainRepository(
            retrofitService = retrofitService,
            context = context
        )
        val viewModel = LoginViewModel(mainRepository)
        return viewModel as T
    }
}
 

Вот мой фрагмент, где я вызываю функцию входа в систему.

 class FirstFragment : Fragment() {

private var _binding: FragmentFirstBinding? = null

private val binding
    get() = _binding!!

private val viewModel: LoginViewModel by activityViewModels {
    LoginViewModelFactory(
        (activity?.application as GameApplication)
    )
}

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    // Inflate the layout for this fragment
    _binding = FragmentFirstBinding.inflate(inflater)

    binding.lifecycleOwner = this

    binding.firstFragment = this

    return binding.root
}

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


    val myPost = LoginBody(
        deviceId = Settings.Secure.getString(
            requireContext().contentResolver, Settings.Secure.ANDROID_ID
        ),
        country = Locale.getDefault().country,
        language = Locale.getDefault().language,
        platform = Build.VERSION.SDK_INT.toString(),
        version = BuildConfig.VERSION_NAME,
        deviceModel = Build.MODEL
    )
    viewModel.login(myPost)
    }
}
 

Logcat

 2021-10-21 16:32:10.676 1520-1520/com.example.a4p1w D/DEBUG: LoginResponseOutsideOfTheViewModelScope : null
2021-10-21 16:32:12.354 1520-1520/com.example.a4p1w D/DEBUG: Login response : 12
 

Ответ №1:

В login функции вы launch используете асинхронную сопрограмму (которая извлекает значение и устанавливает его LiveData перед регистрацией), а затем регистрируете текущее значение LiveData. Эта сопрограмма требует времени, поэтому сначала выполняется инструкция журнала «текущее значение» (вот почему они печатаются в таком порядке).

У LiveData вас изначально нет значения (вы не передаете его в конструктор):

 private val _loginResponse = MutableLiveData<LoginResponse>()
 

поэтому loginResponse.value возвращает значение null, пока вы не установите для него значение (как в вашей сопрограмме). Вот почему это прекрасно работает, когда сопрограмма завершается, и каждый раз, когда вы читаете ее после этого