Home > Software design >  How to read asynchronous data from real-time database using android Kotlin?
How to read asynchronous data from real-time database using android Kotlin?

Time:05-11

Here is my code to read asynchronous data from a real-time database using android Kotlin:

class suDetails : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_su_details)



        su_image.setOnClickListener {
            readData(object : MyCallback {
                override fun onCallback(imageUrl: String?) {
                    if (imageUrl != null) {
                        val imageViewer = Intent(baseContext, suDetails::class.java)
                        imageViewer.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                        imageViewer.putExtra("su_image", imageUrl)
                        startActivity(imageViewer)
                    }
                }
            })
        }

    }

    fun readData(myCallback: MyCallback) {

        val su_resource =intent
        val su_res = su_resource.getStringExtra("su_userid")

        val suRef = FirebaseDatabase.getInstance().getReference().child("Users").child(su_res!!)
        suRef.addValueEventListener(object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                if(dataSnapshot.exists()){
                    su_layout.visibility = View.VISIBLE
                    val userData = dataSnapshot.getValue(profile_model::class.java)

                    val imageUrl = userData!!.getImageUrl()
                    Picasso.get().load(imageUrl).placeholder(R.drawable.ic_baseline_image_200).into(su_image)
                    su_name.text = userData.getnameOfsu()

                    Toast.makeText(baseContext, imageUrl, Toast.LENGTH_LONG).show()
                    
                    myCallback.onCallback(imageUrl)

                }
            }

            override fun onCancelled(error: DatabaseError) {

            }
        })
    }

    interface MyCallback {
        fun onCallback(value: String?)
    }

}

I have referred to other questions to read asynchronous data from a real-time database but when I tried the solution I am not able to show any data in my ImageView and textView. I am getting only the blank screen.

The New code after the answer of Tyler V:

class suDetails : AppCompatActivity() {

    private var currentImageUrl: String = ""
    private var su_res: String = ""

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_su_details)



        su_res = intent.getStringExtra("su_userid").toString()

        // get views
        val su_name = findViewById<TextView>(R.id.su_name)
        val su_image = findViewById<ImageView>(R.id.su_image)

        // onClick launches another activity - if the image
        // hasn't loaded yet nothing happens
        su_image.setOnClickListener { viewCurrentImage() }

        // start the async loading right away - once it is loaded the
        // su_layout view will be visible and the view data
        // will be populated. It might be good to show a progress bar
        // while it's loading
        readData()
    }

    fun readData() {
        println("LOG: called readData")

        Toast.makeText(baseContext, su_res, Toast.LENGTH_LONG).show()

        println("LOG: getting data for ${su_res}")

        val suRef = FirebaseDatabase.getInstance()
            .getReference()
            .child("Users")
            .child(su_res)

        suRef.addValueEventListener(object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                if (dataSnapshot.exists()) {
                    println("LOG: data snapshot exists")

                    su_layout.visibility = View.VISIBLE
                    val userData = dataSnapshot.getValue(profile_model::class.java)

                    currentImageUrl = userData?.getImageUrl() ?: ""
                    su_name.text = userData?.getnameOfsu() ?: ""

                    println("LOG: Got user data ${currentImageUrl}")

                    if (currentImageUrl.isNotEmpty()) {
                        Picasso.get()
                            .load(currentImageUrl)
                            .placeholder(R.drawable.ic_baseline_image_200)
                            .into(su_image)
                    }
                } else {
                    println("LOG: user not found in database")
                }
            }

            override fun onCancelled(error: DatabaseError) {
                println("LOG: cancelled")
            }
        })
    }

    private fun viewCurrentImage() {
        if (currentImageUrl.isEmpty()) return
            Toast.makeText(baseContext, currentImageUrl, Toast.LENGTH_LONG).show()
            val imageViewer = Intent(baseContext, ImageViewer::class.java)
            imageViewer.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            imageViewer.putExtra("su_image", currentImageUrl)
            startActivity(imageViewer)

    }
}

CodePudding user response:

The top answer to this related question shows you how to make callbacks, but that doesn't really answer the question of how to use the async data, and isn't really helpful or relevant to this type of problem.

I don't see anything specifically wrong with your callback - but it silently swallows a number of possible error cases (e.g. if the user doesn't exist). The example below has some print statements that should help determine better what is happening.

A cleaner approach than the extra callback interface is to make a separate method to handle the async result. Here is a cleaned up example of how that might look - with some pseudo-code where parts of your example were missing. To help debug, you should get in the habit of using log or print statements if you don't understand what parts of the code are running, or if something doesn't look the way you expect it to.

private var currentImageUrl: String = ""
private var userId: String = ""
private lateinit var su_name: TextView
private lateinit var su_image : ImageView
private lateinit var su_layout : ConstraintLayout

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_su_details)

    // get views
    su_name = findViewById<TextView>(R.id.su_name)
    su_image = findViewById<ImageView>(R.id.su_image)
    su_layout = findViewById<ConstraintLayout>(R.id.su_layout)

    su_layout.visibility = View.INVISIBLE

    // get user id from intent
    userId = intent.getStringExtra("su_userid").orEmpty()

    // TODO: Handle what to do if userId is empty here!
    if( userId.isEmpty() ) {
        finish()
    }

    // onClick launches another activity - if the image
    // hasn't loaded yet nothing happens
    su_image.setOnClickListener { viewCurrentImage() }

    // start the async loading right away - once it is loaded the
    // su_layout view will be visible and the view data 
    // will be populated. It might be good to show a progress bar
    // while it's loading
    startLoading()
}

private fun startLoading() {
    println("LOG: getting data for ${userId}")

    val suRef = FirebaseDatabase.getInstance()
                  .getReference()
                  .child("Users")
                  .child(userId)

    suRef.addValueEventListener(object : ValueEventListener {
        override fun onDataChange(dataSnapshot: DataSnapshot) {
            if(dataSnapshot.exists()) {
                println("LOG: data snapshot exists")
                val userData = dataSnapshot.getValue(profile_model::class.java)
                showData(userData)
            }
            else {
                println("LOG: user not found in database")
            }
        }

        override fun onCancelled(error: DatabaseError) {
            println("LOG: cancelled")
        }
    })
}

private fun showData(userData: profile_model?) {
    su_layout.visibility = View.VISIBLE

    currentImageUrl = userData?.getImageUrl() ?: ""
    su_name.text = userData?.getnameOfsu() ?: "Error"

    println("LOG: Got user data ${currentImageUrl}")

    if( currentImageUrl.isNotEmpty() ) {
        Picasso.get()
          .load(currentImageUrl)
          .placeholder(R.drawable.ic_baseline_image_200)
          .into(su_image)
    }
}

private fun viewCurrentImage() {
    if( currentImageUrl.isEmpty() ) return

    val imageViewer = Intent(this, suDetails::class.java)
    imageViewer.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    imageViewer.putExtra("su_image", currentImageUrl)
    startActivity(imageViewer)
}
  • Related