Reputation: 3958
Updated code in Edit 2
Edit:
I've found this which I think addresses the same issue but I'm having a hard time understanding exactly what's happening here as it's in Java and my Java is not as good as my kotlin. Any clarification would be appreciated.
Whenever I declare a lateinit variable and then initialize it from a ListenerForSingleValueEvent
, I get an error that it was never initialized. It's like the initialization is bounded by the listener. What I end up doing is a lot of nesting, but it makes the code very messy. Is there a reason for that? A way around it?
For example, in this following code I will get an error saying that userProfile
was never initialized:
class ProfileFragment : Fragment() {
lateinit var userProfile: Users
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_profile, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
arguments?.let {
val safeArgs = ProfileFragmentArgs.fromBundle(it)
val userProfileUid = safeArgs.usersProfile
val refUser = FirebaseDatabase.getInstance().getReference("users/$userProfileUid")
refUser.addListenerForSingleValueEvent(object : ValueEventListener{
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(p0: DataSnapshot) {
userProfile = p0.getValue(Users::class.java)
}
})
}
val uri = userProfile?.image
Picasso.get().load(uri).into(profilePicture)
profileName.text = userProfile?.name
}
}
Edit 2:
After reading Alex Mamo's answer on this Q and the one he attached, I have tried to change my code accordingly but still can't make it to work.
Here's y current code (the elements are different as I've been working on my code but it's exactly the same case)
*I've tried placing the cal for readData with the arguments section or after but neither work for me and I keep getting the error that imageObject wasn't initialized when I try to use it in the onViewCreated
class ImageFullSizeFragment : androidx.fragment.app.Fragment() {
lateinit var refImage: DatabaseReference
lateinit var imageObject: Images
override fun onAttach(context: Context) {
super.onAttach(context)
arguments?.let {
val safeArgs = ImageFullSizeFragmentArgs.fromBundle(it)
val imageId = safeArgs.imageId
refImage = FirebaseDatabase.getInstance().getReference("/images/feed/$imageId")
readData(object : MyImageCallBack {
override fun onCallback(value: Images) {
imageObject = value
}
})
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val mainImage = view.findViewById<ImageView>(R.id.image_full_image)
Picasso.get().load(Uri.parse(imageObject.image)).into(mainImage)
}
fun readData(myImagesCallback: MyImageCallBack) {
refImage.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(p0: DataSnapshot) {
val image = p0.getValue(Images::class.java)
myImagesCallback.onCallback(image!!)
}
})
}
}
And this is the interface:
interface MyImageCallBack {
fun onCallback(value: Images)
}
Upvotes: 1
Views: 281
Reputation: 138969
Firebase APIs are asynchronous
, meaning that onDataChange()
function returns immediately after it's invoked, and the callback from the Task
it returns, will be called some time later. There are no guarantees about how long it will take. So it may take from a few hundred milliseconds to a few seconds before that data is available.
Because that method returns immediately, the value of your userProfile
object that you are getting from the database and you're trying to use it outside the callback, will not have been populated yet.
Basically, you're trying to use a value synchronously from an API that's asynchronous. That's not a good idea. You should handle the APIs asynchronously as intended. So, in this case it has nothing to do with the lateinit
.
A quick solve for this problem would be to move the following lines of code:
val uri = userProfile?.image
Picasso.get().load(uri).into(profilePicture)
profileName.text = userProfile?.name
And use them only inside the callback. In this way the data that it comes from the database will be available.
If you want to use it outside tha callback, I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. It's for Cloud Firestore but same rules apply in case of Firebase realtime database.
Upvotes: 1
Reputation: 2409
For example, in this following code I will get an error saying that userProfile was never initialized.
When onDataChange
is called, userProfile
initialized. But before that you access userProfile
. Therefore, a not initialized exception occurred.
move setupViews
to onDataChange
, try this code
override fun onDataChange(p0: DataSnapshot) {
userProfile = p0.getValue(Users::class.java).also {
Picasso.get().load(it.image).into(profilePicture)
profileName.text = it.name
}
}
Upvotes: 0
Reputation: 60973
In your case, val uri = userProfile?.image
run before userProfile = p0.getValue(Users::class.java)
that why this problem happened.
Something I think it help
code2
below code1
not mean it code2
run after code1
in runtime. addListenerForSingleValueEvent
is asynonymous
Upvotes: 1