0

On my Android app I'm reading data from the Firebase Realtime Database.

Edit: I changed this using callbacks, because that works asynchronously.

I'm trying to use a ViewModel class to store the data from Firebase Database, and to update my Google Maps UI with new markers.

The problem is that I need to subscribe my LiveData object in onCreate(), but I do need to update the map's UI with markers in onMapReady().

Do I need to use callbacks here or maybe the ViewModel is redundant here? Any suggestions could be helpful. I'm trying to work it out for few days with not be able success, in addition I was looking for a lot of questions and answers online and nothing seems to be helpful.

readData function from DatabaseManager:

fun readFromDatabase(type: String,  callBack: OwnerListCallBack) {
    when (type) {
        OWNERS -> {
            ownerRef.addValueEventListener(object : ValueEventListener {
                override fun onDataChange(dataSnapshot: DataSnapshot) {
                    // This method is called once with the initial value and again
                    // whenever data at this location is updated.
                    for (snapshot: DataSnapshot in dataSnapshot.children) {
                        val owner = snapshot.getValue(Owner::class.java)

                        Log.d(TAG, "readFromDatabase: onDataChange: owner's name is ${owner?.name}")
                        if (owner != null)
                            owners.add(owner)
                        Log.d(TAG, "readFromDatabase: onDataChange: owner's name is ${owners[0].name}")
                    }
                }

                override fun onCancelled(error: DatabaseError) {
                    // Failed to read value
                    Log.w(TAG, "Failed to read value.", error.toException())
                }
            })
        }

}

setRestaurantsOnMap function which called in onMapReady:

private fun setRestaurantsOnMap() {
    Log.d(TAG, "setRestaurantsOnMap: called")
    // Set markers on the map. The owner's restaurants.
    database.readFromDatabase(OWNERS)
//        Log.d(TAG, "setRestaurantsOnMap: ${owners[0]}")
    if (database.owners.isNotEmpty()) {
        for (owner in database.owners) {
            try {
                val ownerAddress = owner.getRestaurant().getAddress()
                Log.d(TAG, "setRestaurantsOnMap: owner's address is: ${ownerAddress.getAddress()}")
                Toast.makeText(this, "setRestaurantsOnMap: owner's address is: ${ownerAddress.getAddress()}", Toast.LENGTH_LONG).show()
                val latlng = getLocationFromAddress(this, ownerAddress.getAddress())
                Log.d(TAG, "setRestaurantsOnMap: latitude is - ${latlng?.latitude}, longitude is - ${latlng?.longitude} ")
                Toast.makeText(this, "setRestaurantsOnMap: latitude is - ${latlng?.latitude}, longitude is - ${latlng?.longitude} ", Toast.LENGTH_LONG).show()
                if (latlng != null) {
                    mMap.addMarker(MarkerOptions()
                        .position(latlng)
                        .title(owner.getRestaurant().getName())
                        .snippet(owner.getRestaurant().getAddress().getAddress())
                        .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_CYAN))
                    )
                    mMap.moveCamera(CameraUpdateFactory.newLatLng(latlng))
                } else {
                    Toast.makeText(this, "setRestaurantsOnMap: latlng is null", Toast.LENGTH_SHORT).show()
                }
            } catch (e: NullPointerException) {
                e.printStackTrace()
            } catch (e: IndexOutOfBoundsException) {
                e.printStackTrace()
            }
        }
    }
}

My ViewModel class:

class MapsViewModel : ViewModel() {
private val db = DatabaseManager()

private val owners = MutableLiveData<List<Owner>>()
val ownerList: LiveData<List<Owner>>
get() = owners


init {
    owners.postValue(loadOwners())
    Log.d(TAG, "init: Owners are ${owners.value}")
}


override fun onCleared() {
    Log.d(TAG, "onCleared: canceling pending downloads")
}


private fun loadOwners(): List<Owner> {
    var ownerList = ArrayList<Owner>()

    Log.d(TAG, "loadAddress: called")
    // Set markers on the map. The owner's restaurants.
    db.readFromDatabase(OWNERS, object : OwnerListCallBack {
        @SuppressLint("RestrictedApi")
        override fun onCallBack(owners: ArrayList<Owner>) {
            if (owners.isNotEmpty()) {
                ownerList = owners
                Log.d(TAG, "Owners are ${ownerList[0]}, ${ownerList[1]}, ${ownerList[2]}")
            } else {
                throw DatabaseException("Loaded was not success. Number of owners is ${owners.count()}")
            }
        }
    })
    return ownerList
}
erezlev13
  • 1
  • 1
  • 1
  • 1
    Your `readFromDatabase` is asynchronous. If you run it in a debugger and place some breakpoint/log statements, you'll see that `if (database.owners.isNotEmpty()) {` runs before any of the `owners.add(owner)` calls happens. Any code that requires data from the database, must be inside `onDataChange` or be called from there. For a longer explanation, and code examples, see: https://stackoverflow.com/questions/50434836/getcontactsfromfirebase-method-return-an-empty-list/50435519#50435519 – Frank van Puffelen Apr 09 '20 at 16:14
  • Thank you! @Frank van Puffelen – erezlev13 Apr 09 '20 at 18:43
  • @erezlev13 You can **[this](https://stackoverflow.com/a/51595202/5246885)** and **[this](https://stackoverflow.com/a/59124705/5246885)** out. – Alex Mamo Apr 10 '20 at 08:47

0 Answers0