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