How do i make my code in kotlin read data from the firebase firestore - firebase

I am trying to make an app that reads data from the firebase firestore and then shows in a screen that same data
The problem the data only appears when shown in log but i want the in text(string)
Can anyone help me understand how to do it
I already tried many videos explaining but none of then work so my last option is really ask in here for help.
Here is the code
#SuppressLint("UnrememberedMutableState")
#Composable
fun DB () {
val db = Firebase.firestore
val collectionReference = db.collection("Inventário")
.document("Bloco E")
.collection("Sala E0.05")
val data = mutableStateOf(mapOf<String, Any>())
val job = remember { Job() }
remember {
GlobalScope.launch(Dispatchers.Main) {
val documentSnapshot = collectionReference.document("Computador").get().await()
try {
data.value = documentSnapshot.data ?: mapOf()
}catch (e: Exception){
Log.e("Firestore", "Error retrieving data", e)
}
}
}
Column() {
data.value.forEach { (key, value) ->
Text("$key: $value")}
}
}
And here is the database structure:

It seems like you forgot to use remember on your MutableState (and also suppressed the lint warning with (#SuppressLint("UnrememberedMutableState"))):
val (data, setData) = remember { mutableStateOf(mapOf<String, Any>()) }
Also, using GlobalScope in Android is not recommended. Consider using a LaunchedEffect instead:
#Composable
fun DB () {
val db = Firebase.firestore
val collectionReference = db.collection("Inventário")
.document("Bloco E")
.collection("Sala E0.05")
val (data, setData) = remember { mutableStateOf(mapOf<String, Any>()) }
LaunchedEffect(collectionReference) {
try {
val documentSnapshot = collectionReference.document("Computador").get().await()
setData(documentSnapshot.data ?: mapOf())
} catch (e: Exception) {
Log.e("Firestore", "Error retrieving data", e)
}
}
Column() {
data.value.forEach { (key, value) ->
Text("$key: $value")}
}
}

Related

firestore, coroutine and flow

firebase method is working on worker thread automatically. but I have used coroutine and callbackflow to implement firebase listener code synchronously or get return from the listener.
below is my code that I explained
coroutine await with firebase for one shot
override suspend fun checkNickName(nickName: String): Results<Int> {
lateinit var result : Results<Int>
fireStore.collection("database")
.document("user")
.get()
.addOnCompleteListener { document ->
if (document.isSuccessful) {
val list = document.result.data?.get("nickNameList") as List<String>
if (list.contains(nickName))
result = Results.Exist(1)
else
result = Results.No(0)
//document.getResult().get("nickNameList")
}
else {
}
}.await()
return result
}
callbackflow with firebase listener
override fun getOwnUser(): Flow<UserEntity> = callbackFlow{
val document = fireStore.collection("database/user/userList/")
.document("test!!!!!")
val subscription = document.addSnapshotListener { snapshot,_ ->
if (snapshot!!.exists()) {
val ownUser = snapshot.toObject<UserEntity>()
if (ownUser != null) {
trySend(ownUser)
}
}
}
awaitClose { subscription.remove() }
}
so I really wonder these way is good or bad practice and its reason
Do not combine addOnCompleteListener with coroutines await(). There is no guarantee that the listener gets called before or after await(), so it is possible the code in the listener won't be called until after the whole suspend function returns. Also, one of the major reasons to use coroutines in the first place is to avoid using callbacks. So your first function should look like:
override suspend fun checkNickName(nickName: String): Results<Int> {
try {
val userList = fireStore.collection("database")
.document("user")
.get()
.await()
.get("nickNameList") as List<String>
return if (userList.contains(nickName)) Results.Exist(1) else Results.No(0)
} catch (e: Exception) {
// return a failure result here
}
}
Your use of callbackFlow looks fine, except you should add a buffer() call to the flow you're returning so you can specify how to handle backpressure. However, it's possible you will want to handle that downstream instead.
override fun getOwnUser(): Flow<UserEntity> = callbackFlow {
//...
}.buffer(/* Customize backpressure behavior here */)

Adding document İD to firebase document as a field

I have a model with default values. My app gets the data from user through EditTexts and add them to Firebase Firestore. I hava an addData function (in AddAnalyzeActivity) and savefunction (in AddAnalyzeViewModel) for this operation. I'm getting EditText entries in AddAnalyzeActivity and adding them to my model but on this step ı want to add document id to my model but I can't access the documentIds properly in AddAnalyzeActivity. I can only access them with a forEach method when I try to retrieving the mentioned data with retrieveData function (in PairDetailVM) from Firestore but If I try to add document Ids in retrieveData method it only adds default value of documentId.
What I tried to:
Using #DocumentId annotation in my model.
Setting null default value of documentId in my model.
Getting a list of all documents' ids but can't match them with actual items.
Here is the screenShot for logic:
AnalyzeModel:
data class AnalyzeModel(
var concept: String?="",
var reason: String?="",
var result: String?="",
var rrRatio: Double?=0.0,
var tarih: Timestamp=Timestamp.now(),
var tradingViewUrl: String="",
var id : String="")
addData :
fun addData(view: View) {
val tarih = com.google.firebase.Timestamp.now()
val rr = rrText.text.toString()
var doubleRR = rr.toDoubleOrNull()
if (doubleRR == null) { doubleRR = 0.0 }
val analyzeDTO = AnalyzeModel(
conceptText.text.toString(),
reasonForText.text.toString(),
resultAddingText.text.toString(),
doubleRR,
tarih,
chartImage.text.toString()
)
viewModel.save(analyzeDTO)
val intent = Intent(this, PairDetailActivity::class.java)
startActivity(intent)
finish()
}
save :
fun save(data: AnalyzeModel) {
database.collection(dbCollection!!).document("Specified").collection("Pairs")
.document(chosenPair!!)
.collection("Analysis")
.add(data)
.addOnFailureListener { exception ->
exception.printStackTrace()
Toast.makeText(getApplication(), exception.localizedMessage, Toast.LENGTH_LONG).show()
}
}
retrieveData:
private fun retrieveData() {
val docRef = collectionRef.orderBy("tarih", Query.Direction.DESCENDING)
docRef.addSnapshotListener { value, error ->
try {
if (value != null && !value.isEmpty) {
val allAnalysis= ArrayList<AnalyzeModel>()
val documents = value.documents
documents.forEach {
val analyze = it.toObject(AnalyzeModel::class.java)
if (analyze!=null){
allAnalysis.add(analyze)
}
}
list.value = allAnalysis
} else if (error != null) {
Toast.makeText(Application(), error.localizedMessage, Toast.LENGTH_LONG).show()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
If you want to save the document ID into the document itself, consider separating the creation of the new DocumentReference from writing to it, by using set instead of add.
fun save(data: AnalyzeModel) {
val newRef = database.collection(dbCollection!!).document("Specified").collection("Pairs")
.document(chosenPair!!)
.collection("Analysis")
.document() // 👈 generates a new reference with a unique ID
data.id = newRef.id // 👈 set the ID into your object
newRef.set(data) // 👈 writes the data to the new reference
.addOnFailureListener { exception ->
exception.printStackTrace()
Toast.makeText(getApplication(), exception.localizedMessage, Toast.LENGTH_LONG).show()
}
}
Also see the second snippet in the documentation on adding a document

Checking if username already exists gives error kotlin

I translated the code below from java, but I am getting an error in some places, why is it giving an error, is there a point I missed?
val mQuery: Query = firestore.collection("users")
.whereEqualTo("nickname", mUserName)
mQuery.addSnapshotListener(object : EventListener<QuerySnapshot> {
fun onEvent(
documentSnapshots: QuerySnapshot,
e: FirebaseFirestoreException?
) {
for (ds in documentSnapshots) {
if (ds != null) {
val userName: String = document.getString("username")
Log.d(
TAG,
"checkingIfusernameExist: FOUND A MATCH: $userName"
)
Toast.makeText(
this#SignUpActivity,
"That username already exists.",
Toast.LENGTH_SHORT
).show()
}
}
}
})
I've been doing the things described here for 2-3 days, but it keeps throwing an error.Event listeners and docs throw errors.
I translated the code below from java
That's not the correct way of "translating" that code from Java to Kotlin, since that answer provides a solution for getting data only once. So to be able to do that in Kolin programming language please use the following lines of code:
val rootRef = FirebaseFirestore.getInstance()
val allUsersRef = rootRef.collection("all_users")
val userNameQuery = allUsersRef.whereEqualTo("username", "userNameToCompare")
userNameQuery.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
for (document in task.result) {
if (document.exists()) {
Log.d("TAG", "username already exists")
val userName = document.getString("username")
//Do what you need to do with the userName
} else {
Log.d("TAG", "username does not exists")
}
}
} else {
Log.d("TAG", "Error getting documents: ", task.exception)
}
}

Kotlin Flow - Generic function for Retrofit with Result sealed class

I am recently working with Flow in my retrofit's repository.
Sealed class for Result
enum class ApiStatus{
SUCCESS,
ERROR,
LOADING
}
sealed class ApiResult <out T> (val status: ApiStatus, val data: T?, val message:String?) {
data class Success<out R>(val _data: R?): ApiResult<R>(
status = ApiStatus.SUCCESS,
data = _data,
message = null
)
data class Error(val exception: String): ApiResult<Nothing>(
status = ApiStatus.ERROR,
data = null,
message = exception
)
data class Loading<out R>(val _data: R?, val isLoading: Boolean): ApiResult<R>(
status = ApiStatus.LOADING,
data = _data,
message = null
)
}
Example repository call for 3 state - Loading, Error, Success
fun googleDisconnect() = flow {
emit(ApiResult.Loading(null, true))
val call = userDataSource.self("v4").googleDisconnect()
if(call.isSuccessful) {
emit(ApiResult.Success(call.body()))
} else {
emit(ApiResult.Error("Google Disconnect Failed"))
}
}
However, I have multiple network call with different function in my repository. Is there any idea to write a generic function for these flow so that these flow can be emitted to the flow builder?
My attempt but problem is How can I pass suspend function into the function?
Finally I got myself the answer. I wonder if this will helps but I will post out my answer.
fun <T> toResultFlow(call: suspend () -> Response<T>?) : Flow<ApiResult<T>?> {
return flow {
emit(ApiResult.Loading())
val c = call() <-- have to initialize the call method first
c?.let {
try{
if(c.isSuccessful) {
c.body()?.let {
emit(ApiResult.Success(it))
}
} else {
c.errorBody()?.let {
val error = it.string()
it.close()
emit(ApiResult.Error(error))
}
}
}catch (e: Exception) {
emit(ApiResult.Error(e.toString()))
}
}
}.flowOn(Dispatchers.IO)
}
Then, pass in your suspend function as lambda
fun googleDisconnect() = toResultFlow {
userDataSource.self("v4").googleDisconnect()
}
Finally, the toResultFlow will be return Flow<ApiResult> and T is your preferred datatype! Volla!

Issue with coroutines fold function and Firestore

thank you for taking your time to read my problem.
Im currently using Firebase Firestore to retrieve a list of objects that I which to display to the UI, im trying to use a suspend function to fold the accumulative values of a sequence of calls from the Firestore server, but at the moment im unable to pass the result value outside the scope of the coroutine.
This is my fold function:
suspend fun getFormattedList(): FirestoreState {
return foldFunctions(FirestoreModel(""), ::getMatchesFromBackend, ...., ....)
}
This is my custom fold function:
suspend fun foldFunctions(model: FirestoreModel,
vararg functions: suspend (FirestoreModel, SuccessData) -> FirestoreState): FirestoreState {
val successData: SuccessData = functions.fold(SuccessData()) { updatedSuccessData, function ->
val status = function(model, updatedSuccessData)
if (status !is FirestoreState.Continue) {
return status
}
updatedSuccessData <--- I managed to retrieve the list of values correctly here
}
val successModel = SuccessData()
successData.matchList?.let { successModel.matchList = it }
successData.usermatchList?.let { successModel.usermatchList = it }
successData.formattedList?.let { successModel.formattedList = it }
return FirestoreState.Success(successModel) <--- I cant event get to this line with debugger on
}
This is my first function (which is working fine)
suspend fun getMatchesFromBackend(model: FirestoreModel, successData: SuccessData): FirestoreState {
return try {
val querySnapshot: QuerySnapshot? = db.collection("matches").get().await()
querySnapshot?.toObjects(Match::class.java).let { list ->
val matchList = mutableListOf<Match>()
list?.let {
for (document in it) {
matchList.add(Match(document.away_score,
document.away_team,
document.date,
document.home_score,
document.home_team,
document.match_id,
document.matchpoints,
document.played,
document.round,
document.tournament))
}
successData.matchList = matchList <--- where list gets stored
}
}
FirestoreState.Continue
} catch (e : Exception){
when (e) {
is RuntimeException -> FirestoreState.MatchesFailure
is ConnectException -> FirestoreState.MatchesFailure
is CancellationException -> FirestoreState.MatchesFailure
else -> FirestoreState.MatchesFailure
}
}
}
My hypothesis is that the suspen fun get cancelled and the continuation of the scope gets blocked, I have tried to use runBlocking { } without vail. If someone has an idea of how to circumvent this issue I'd be very gratefull.

Resources