I have a data class for data that come from user entries. İt is carrying this data to Firebase. This data class also includes documentId variable which is a empty string by default. I want to add document Id's that Firebase created automatically. I tried every way I could think of. But it takes default value in any way.
Here are the four code snippets about this issue. Data class, adding data activity, and retrieving data activity and their View Models.
Dataclass:
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="")
AddAnalyzeActivity, addData function:
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()
}
AddAnalyze ViewModel, save function:
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()
}
}
PairViewModel, retrieveData function:
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()
}
}
}
I want to add document IDs that Firebase created automatically.
To solve this, you only need to annotate the field with #DocumentId.
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="",
#DocumentId 👈
var id : String=""
)
Be also sure to have the latest version of Firestore.
Related
Unable to extract information from the datasnapshot received from firebase.
Currently, I am able to get the dataSnapshot from firebase, but I am having problems extracting the information from it.
In the example below I have a lobby with the code "81MUB" and inside I have a list of players (only using one player in the example). Data from FireBase
{
"81MUB": [
{
"name": "Alejandro",
"points": 0
}
]
}
Data Class
data class Player(
val name: String,
val points: Int
)
Listener
fun getCode(): String {
val index = ('A'..'Z') + ('1'..'9')
var code = ""
for (i in 0..4){
code += index[Random().nextInt(index.size)]
}
return code
}
class MviewModel : ViewModel() {
private val _Players: MutableLiveData<MutableList<Player>> =
MutableLiveData(mutableListOf<Player>(Player("Alejandro", 0)))
private var _LobbyCode: String = ""
private val dataBase = FirebaseDatabase.getInstance()
fun getPlayer(): MutableLiveData<MutableList<Player>> = _Players
fun createLobby() {
_LobbyCode = getCode()
}
fun listener() {
val postListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Post failed, log a message
}
}
dataBase.reference.child(_LobbyCode).addValueEventListener(postListener)
}
}
Any tips?
Each time you call getCode() you are generating a new random code. When reading data, you always use the exact same code that exists in the database. So in code, it should look like this:
val db = Firebase.database.reference
val codeRef = db.child("81MUB")
codeRef.get().addOnCompleteListener {
if (it.isSuccessful) {
val snapshot = it.result
val name = snapshot.child("name").getValue(String::class.java)
val points = snapshot.child("points").getValue(Long::class.java)
Log.d("TAG", "$name/$points")
} else {
Log.d("TAG", error.getMessage()) //Never ignore potential errors!
}
}
The result in the logcat will be:
Alejandro/0
If you however want to map the 81MUB node into an object of type Player, then your data class should look like this:
data class Player(
val name: String? = null,
val points: Int? = null
)
And in code:
val db = Firebase.database.reference
val codeRef = db.child("81MUB")
codeRef.get().addOnCompleteListener {
if (it.isSuccessful) {
val snapshot = it.result
val player = snapshot.getValue(Player::class.java)
Log.d("TAG", "${player.name}/${player.points}")
} else {
Log.d("TAG", error.getMessage()) //Never ignore potential errors!
}
}
Which will produce the exact same output as above.
You might also take into consideration, using the DatabaseReference#push() method which:
Create a reference to an auto-generated child location. The child key is generated client-side and incorporates an estimate of the server's time for sorting purposes.
Instead of using your codes.
Would like to reduce the number of reads in my app with Firestore. So I created a document in my collection with the following structure to hold values for 10 "records", that I will get with a single read - since the size of this document is pretty decent, no worries about the 1MB limit size for a single document. I am updating the content of this document with cloud functions.
name of collection: helperz
name of document: top10
field name in top10: tresholdCounter - this I need to check if a single map should be added to top10 or not
field name in top10: top10 . . array of maps
helperz/top10/tresholdCounter
helperz/top10/top10/0/author
helperz/top10/top10/0/name
helperz/top10/top10/0/url
helperz/top10/top10/1/author
helperz/top10/top10/1/name
helperz/top10/top10/1/url
helperz/top10/top10/2/author
helperz/top10/top10/2/name
helperz/top10/top10/2/url
helperz/top10/top10/3/author
helperz/top10/top10/3/name
helperz/top10/top10/3/url
helperz/top10/top10/4/author
helperz/top10/top10/4/name
helperz/top10/top10/4/url
..
helperz/top10/top10/10/author
helperz/top10/top10/10/name
helperz/top10/top10/10/url
I have a data class . . like this:
data class MyClass(
var name: String? = null,
var url: String? = null,
var author: String? = null,
var counter: Int = 0,
var free: Boolean? = false,
var global: Boolean?=true,
var poses: ArrayList<MyPoze>? = null,
var docId: string? = null,
var category: ArrayList<String>? = null,
#get:PropertyName(CREATED)
#set:PropertyName(CREATED)
#ServerTimestamp var created: Timestamp? = null
)
There are some other fields as well, but for the purpose of this problem, that should be ok.
I have a code for retrieving data from Firestore (in my viewModel):
private fun getHelperzTop10() = viewModelScope.launch {
Log.d("MP23", "getHelperzTop10")
val docRef = db.collection("helperz").document("top10")
docRef.addSnapshotListener { snapshot, e ->
if (e != null) {
Log.w("MP23", "Listen failed.", e)
return#addSnapshotListener
}
if (snapshot != null && snapshot.exists()) {
val docSize = firestoreDocument.getSize(snapshot);
Log.d("MP23","docSize in Bytes: $docSize, in KB: ${docSize * 0.001}, in MB: ${docSize * 0.000001}"
)
val top10 = snapshot.data
Log.d("MP23", top10!!::class.simpleName.toString() )
if ("top10" in keys) {
val top10arr = top10["top10"] as ArrayList<MyClass>
Log.d("MP23", "we have top10 in top10")
Log.d("MP23", top10arr!!::class.simpleName.toString())
Log.d("MP23", top10arr.toString())
//// 👇 PROBLEM HERE ///
// here I need somehow to cast this array of maps as MyClass, but I am getting error:
// java.util.ArrayList cannot be cast to java.lang.MyClass
// or
// java.util.ArrayList cannot be cast to java.lang.Object[] - with some other versions
}
} else {
Log.d("MP23", "Current data: null")
}
}
}
If someone can suggest how can I cast result to MyClass, that would be great. In normal situation (where I have documents that I query against) nicely as single docs, I can easily do:
val singleRec = singleRec.toObject(MyClass::class.java)
If you want to get the top10 array as a List<MyClass>, the simplest solution that I can think of would be to create another class:
data class Document(
var top10: MutableList<MyClass>? = null
)
And read the content of the document like this:
docRef.get().addOnCompleteListener {
if (it.isSuccessful) {
val snapshot = it.result
snapshot?.let {
val document = snapshot.toObject(Document::class.java)
document?.let {
val top10 = document.top10
top10?.let {
for (myClass in top10) {
val name = myClass.name
Log.d("TAG", "$name")
}
}
}
}
} else {
it.exception?.message?.let { message ->
Log.d("TAG", message) //Never ignore potential errors!
}
}
}
I will also recommend you read the following resource for a better understanding:
How to map an array of objects from Cloud Firestore to a List of objects?
When I use the code below I can get data from firebase but when I want to access it with MVVM it returns null.
database.collection("Order")
.get()
.addOnCompleteListener { it ->
if (it.isSuccessful) {
val itemName = it.result.documents[0].data?.get("itemName")
val id = it.result.documents[0].data?.get("id")
It returns null inside Order.kt. I don't realize what the problem is there. I can't find any similar questions here.
FirebaseOrderService.kt
object FirebaseOrderService {
private const val TAG = "FirebaseOrderService"
suspend fun getOrderData(): Order? {
val db = FirebaseFirestore.getInstance()
return try {
db.collection("Order")
.document().get().await().toOrder()
} catch (e: Exception) {
Log.e(TAG, "Error getting order details", e)
FirebaseCrashlytics.getInstance().log("Error getting order details")
FirebaseCrashlytics.getInstance().setCustomKey("id", "1")
FirebaseCrashlytics.getInstance().recordException(e)
null
}
}
SuccessShoppingViewModel.kt
class SuccessShoppingViewModel: ViewModel() {
private val _orderList = MutableLiveData<Order>()
val order: LiveData<Order> = _orderList
init {
viewModelScope.launch {
_orderList.value = FirebaseOrderService.getOrderData()
_orderList
}
}
Order.kt
#Parcelize
data class Order(
val id: String = "",
val picUrl: String = "",
val itemName: String = "",
val itemPrice: Double = 0.0,
val itemAmount: String = "",
val itemQuantatiy: Int = 0
) : Parcelable {
companion object {
fun DocumentSnapshot.toOrder(): Order? {
return try {
val id = getString("id")!!
val picUrl = getString("picUrl")!!
val itemName = getString("itemName")!!
val itemPrice = getLong("itemPrice")?.toDouble()!!
val itemAmount = getString("itemAmount")!!
val itemQuantatiy = getLong("itemQuantatiy")?.toInt()!!
Order(id, picUrl, itemName, itemPrice, itemAmount, itemQuantatiy)
} catch (e: Exception) {
Log.e(TAG, "Error converting order", e)
FirebaseCrashlytics.getInstance().log("Error converting order")
FirebaseCrashlytics.getInstance().setCustomKey("id", id)
FirebaseCrashlytics.getInstance().recordException(e)
null
}
}
private const val TAG = "Order"
}
}
You're getting null because of the following line of code:
db.collection("Order")
.document().get().await().toOrder()
When you are using the above line of code, it means that you are creating a reference to a document with a random ID. Calling CollectionReferenc#document() method, without passing any arguments:
Returns a DocumentReference pointing to a new document with an auto-generated ID within this collection.
So what you're actually doing, you're creating a reference that points to a document that doesn't exist. To solve this problem, you have to pass the ID of the document to the document() function like this:
db.collection("Order")
.document("eBW6...zIO1").get().await().toOrder()
// 👆
I would like to ask for help on how to retrieve data from Firestore for nested Array of Maps called "cities" into MutableList , which I then want to insert into recycler view, where the data from the “regions” are for the header and data “cities” for the regular list items.
Data for regions: MutableList , when I follow the procedure https://medium.com/firebase-tips-tricks/how-to-map-an-array-of-objects-from-cloud-firestore-to-a-list -of-objects-122e579eae10 by Alex Mamo, got fine, but data for: cities: MutableList , according same approach, is null (unable to retrive).
Can you please advise how to get data for “cities”?
P.s. somewhere I read the recommendation to iterate over "cities", but I have no idea how, please go straight for an example (ideally in Kontlin).
Code:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
…..
regionsRef.get().addOnCompleteListener { document ->
if (document.isSuccessful()) {
val documentSnapshot = document.result
// Retrieve array of maps for „regions“
val regions = documentSnapshot.toObject(RegionDocument::class.java)?.regions
// Retrieve array of maps for „cities“
val cities = documentSnapshot.toObject(CityDocument::class.java)?.cities
…
}
}
Data classes for object City:
data class City(
val cityNumber: Long? = null,
val cityName: String? = "" )
data class CityDocument(
var cities: MutableList<City>? = null)
Firestore structure:
To be able to get the data that corresponds to your document structure, you need three classes:
class Document {
var regions: MutableList<Region>? = null
}
class Region {
var cities: MutableList<City>? = null
var regionName: String? = null
var regionNumber: Long? = null
}
class City {
var cityName: String? = null
var cityNumber: Long? = null
}
And below you can find a solution for reading all cities:
val db = FirebaseFirestore.getInstance()
val docIdRef = db.collection("collName").document("docId")
docIdRef.get().addOnCompleteListener { task ->
if (task.isSuccessful) {
val document = task.result
if (document != null) {
val doc = document.toObject(Document::class.java)
if (doc != null) {
val regions = doc.regions
if (regions != null) {
for (region in regions) {
val cities = region.cities
//Do what you need to to do with your List<City>.
}
}
}
}
} else {
Log.d("TAG", task.exception!!.message!!) //Never ignore potential errors!
}
}
Now, simply replace collName and docId with the one you have in your database.
I've view model with MutableLiveData that look like this:
private val _num = MutableLiveData<Float>()
val num: LiveData<Float>
get() = _num
val sum: Int= sumMap.map { it.value }.sumBy { it.num}
_num.value = sum.toFloat()
I'm able to get the value correctly, but when I tried to access through the viewmodel objec, I get
kotlin.KotlinNullPointerException
Why I'm getting this error? my code in my fragment look like this:
val dVal: Float = viewModel.num.value!!
Log.i("MainScreenFragment", "numVal $dVal")
Also, If I remove the !! assertion, I get required Float but found Float?, Why is this? I didn't declare my variable at any point as nullable
EDIT:
This is the entire function, this function called inside init block
private fun getPlacesCountries() {
coroutineScope.launch {
var getPlacesDeffers = PlaceApi.retrofitService.getPropeties()
try {
var result = getPlacesDeffers.await()
if (result.isNotEmpty()) {
val sumMap: Map<String, Data> = result.mapValues { (country,data) -> data.last() }
val sum: Int= sumMap.map { it.value }.sumBy { it.num }
_num.value = sum.toFloat()
}
}
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}
}
}
In your activity/fragment you need to observe LiveData:
viewModel.num.observe(this, Observer {
it?.let { chart.setData(it) } // set values if it is not null
})