“Lateinit was never initialized” while it is actua

2019-08-01 00:16发布

问题:

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

回答1:

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.



回答2:

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.
  • "Lateinit was never initialized" is runtime error happened when you try to use a variable when it is not initialized in runtime not compile time.
  • addListenerForSingleValueEvent is asynonymous


回答3:

For example, in this following code I will get an error saying that userProfile was never initialized.

Problem

When onDataChange is called, userProfile initialized. But before that you access userProfile. Therefore, a not initialized exception occurred.

Solution

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