I'm trying to handle a LiveData
value (profilePicture: Bitmap
) within a BindingAdapter
function but the first value is always null. The binding adapter is a ViewSwitcher
with ProgressBar
and ImageView
.
Getting profile picture from firebase like this:
val downloadPictureResult = MutableLiveData<Bitmap>()
// ...
fun downloadProfilePic(): LiveData<Bitmap?> {
val imageRef = storage.getReference("images/$uid/profile/profile_picture.jpg")
imageRef.getBytes(ONE_MEGABYTE).addOnCompleteListener { task ->
if (task.isSuccessful) {
//...
downloadPictureResult.value = responseBitmap
//...
} else {
downloadPictureResult.value = null
Log.d(TAG, task.exception?.localizedMessage)
}
}
return downloadPictureResult;
}
Because the first value is null and the second the expected bitmap object, the view.showNext()
is called two times. But it's more important for me to understand why the firstvalue is null because the setProfilePicture
method will have some more logic.
BindingAdapter looks like this.
fun setProfilePicture(view: ViewSwitcher, profilePicture: Bitmap?) {
Log.d("PPSS", profilePicture.toString())
val imageView: ImageView = view[1] as ImageView
imageView.setImageDrawable(view.context.getDrawable(R.drawable.account_circle_24dp))
profilePicture.let { picture ->
if (picture != null) {
val rounded = RoundedBitmapDrawableFactory.create(view.resources, picture)
rounded.isCircular = true
imageView.setImageDrawable(rounded)
view.showNext()
} else {
view.showNext()
}
}
Log:
2019-04-13 17:53:01.658 11158-11158/... D/PPSS: null
2019-04-13 17:53:02.891 11158-11158/... D/PPSS: android.graphics.Bitmap@8b6ecb8
When you define a LiveData
, its initial value will be null even when its type is not nullable:
val downloadPictureResult = MutableLiveData<Bitmap>()
// Here, downloadPictureResult.value is null
In your case, the value of the LiveData
returned by downloadProfilePic()
will be null until the picture is downloaded, which will happen asynchronously inside the callback addOnCompleteListener
:
fun downloadProfilePic(): LiveData<Bitmap?> {
...
imageRef.getBytes(ONE_MEGABYTE).addOnCompleteListener { task ->
...
// This happens asynchronously, likely after downloadProfilePic()
// has already returned
downloadPictureResult.value = responseBitmap
...
}
return downloadPictureResult;
}
That's why the first value that gets passed to your adapter is null, because downloadPictureResult.value
is still null at the time when downloadPictureResult
is returned by downloadProfilePic()
for the first time.
Let's understand it step by step about how LiveData
works in your case (In generally):
downloadProfilePic()
returns LiveData
of nullable Bitmap
as return type. Method contains asynchronous code of addOnCompleteListener
meaning that it's execution would be happen even after LiveData
value has been returned.Here:
fun downloadProfilePic(): LiveData<Bitmap?> {
val imageRef = storage.getReference("images/$uid/profile/profile_picture.jpg")
imageRef.getBytes(ONE_MEGABYTE).addOnCompleteListener { task ->
if (task.isSuccessful) {
//...
downloadPictureResult.value = responseBitmap
//...
} else {
downloadPictureResult.value = null
Log.d(TAG, task.exception?.localizedMessage)
}
}
return downloadPictureResult;
}
You're returning downloadPictureResult
as result of method which is initialized globally as val downloadPictureResult = MutableLiveData<Bitmap>()
mutable livedata, so that we can modify it afterwards. And that happens inside callback of addOnCompleteListener
.
Now presumably, when your view (Activity/Fragment)
gets loaded and hence you've added your live data from ViewModel
as DataBinding it gets initial value whatever is there in LiveData
and then observes it further.
So here, you've initialized LiveData
with null value of Bitmap
during this code val downloadPictureResult = MutableLiveData<Bitmap>()
it returns null value for very first time.
And then callback happens from addOnCompleteListener
method and value is assigned livedata as downloadPictureResult.value = responseBitmap
or null eventually if error.
That's why there are two calls for
LiveData
in yourBindingAdapter
methodsetProfilePicture()
.
Note: Simple hack you could do if you don't want two callback is making your callback synchronous instead of asynchronous.
As the other answers have told you, the first value will be always null. However, you can get the value when it's ready using an observer and then unsubscribe from it when you have your image.
val pic = downloadProfilePic()
pic.observe(owner, object : Observer<BitMap?> {
override fun onChanged(b: BitMap?){
if(b != null){
setProfilePicture(yourView, b)
pic.removeObserver(this)
}
}
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With