Home > OS >  LiveData value returns null
LiveData value returns null

Time:10-22

Why my external immutable LiveData value returns null when I want to receive it outside of the viewModelScope?

As you can see in logcat there is no problem when I want to receive it in viewModelScope or call login function second time.

Here is my view model

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

Here is my Fragment where I call login function.

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

CodePudding user response:

In the login function you launch an async coroutine (which fetches a value and sets it on the LiveData before logging it), and then you log the LiveData's current value. That coroutine takes time, so the "current value" log statement runs first (that's why they print in that order).

Your LiveData doesn't initially have a value (you're not passing one into the constructor):

private val _loginResponse = MutableLiveData<LoginResponse>()

so loginResponse.value returns null, until you set a value on it (like in your coroutine). That's why it works fine when the coroutine completes, and every time you read it after that

  • Related