I am very sorry if I break some rules, or if this has already been asked before. I have used so much time to google examples, and questions on stack overflow and other recourses. But I can simply not understand how I can get a document field from a firestore collection, and show the string value in a jetpack compose text function.
I am a very beginner in programming and Android. So I properly has some fundamental misunderstanding how I should do it but here is my attempt which doesn't work, and I can not understand why.
In Firestore I have a collection, called users with a document called holidaySavings that has a field of type string called name.
I want to show the value of name in a composable text function.
I have a class called storedData that handles Firestore. It has methods for creating and update a collection /document /fields. That works.
But I cant seem to be able to read a field value from a document to a jetpack composable text.
I can read the value from a document field, to the Log in Android studio.
Here is my function in my class where I handle the Firestore database
fun readDataTestFinal(): String{
val docRef = db.collection("users").document("holidaySavings")
var returnTest = ""
docRef.get()
.addOnSuccessListener { document ->
if (document != null) {
Log.d("Rtest", "DocumentSnapshot data: ${document.data}")
// I want to return this so I can use it in a composable text view
returnTest = document.get("name").toString()
} else {
Log.d("Rtest", "No such document")
}
}
.addOnFailureListener { exception ->
Log.d("Rfail", "get failed with ", exception)
}
return returnTest
}
And here I try to read the value into a jetpack compose Text function.
var newStringFromStoredData by remember {
mutableStateOf(storedData().readDataTestFinal())
}
Text(
modifier = Modifier.background(color = Color.Blue),
text = newStringFromStoredData
)
When I run the app. everything compiles fine, and I get the value from the document field fine, and can see it in the Log in Android Studio.
But the Compose function where call Text with the value newStringFromStoredData it doesn't show on the screen?
Can anyone tell me what it is I don't understand, and how it could be done so I can use the firestore document field and show the value in a jetpack compose Text function?
CodePudding user response:
The Firebase call is async, which means it will not return the data immediately. This is why the API uses a callback. Therefore, your function readDataTestFinal
is always returning an empty string.
One solution you can use is transform your function in a suspend
function and call it using a coroutine scope. For example:
suspend fun readDataTestFinal(): String {
val docRef = firestore.collection("users")
.document("holidaySavings")
return suspendCoroutine { continuation ->
docRef.get()
.addOnSuccessListener { document ->
if (document != null) {
continuation.resume(document.get("name").toString())
} else {
continuation.resume("No such document")
}
}
.addOnFailureListener { exception ->
continuation.resumeWithException(exception)
}
}
}
In the code above, we are converting a callback call to suspend function.
In your composable, you can do the following:
var newStringFromStoredData by remember {
mutableStateOf("")
}
Text(newStringFromStoredData)
LaunchedEffect(newStringFromStoredData) {
newStringFromStoredData =
try { readDataTestFinal() } catch(e: Exception) { "Error!" }
}
The LaunchedEffect
will launch your suspend function and update the result as soon it loads.
A better option would be define this call in a View Model and call this function from it. But I think this answers your question and you can improve your architecture later. You can start from here.
CodePudding user response:
The most convenient, quickest, and apparently the best-in-your-case patch would be to use what are called valueEventListeners
.
Firebase provides these helpful methods for you, so that you can keep your app's data up-to-date with the firebase servers.
val docRef = db.collection("cities").document("SF")
docRef.addSnapshotListener { snapshot, e -> // e is for error
// If error occurs
if (e != null) {
Log.w(TAG, "Listen failed.", e)
return@addSnapshotListener
}
// If backend value not received, use this to get current local stored value
val source = if (snapshot != null && snapshot.metadata.hasPendingWrites())
"Local"
else
"Server"
// If request was successful,
if (snapshot != null && snapshot.exists()) {
Log.d(TAG, "$source data: ${snapshot.data}")
//Update your text variable here
newStringFromStoredData = snapshot.data // Might need type-conversion
} else {
Log.d(TAG, "$source data: null")
}
}
This will not only solve your problem as described in the question, but will also ensure that whenever the value on the server is changed/updated, your text will update alongside it. It is usually a good best practice to use these listeners, and these are often converted into LiveData
objects for respecting the 'separation-of-concerns' principle, but you can use this simple implementation for the simple use-case described.
Another thing, this would usually go in a viewModel, and hence, you should declare you text variable inside the viewmodel too.
Try it in the init
block.
calss MVVM: ViewModel() {
init {
/* Paste Code From Above Here */
}
var newStringFromStoredData by mutableStateOf("")
}
Then read it in the Composable
Text(viewModel.newStringFromStoredData)