Cannot fill a MutableLiveData of type ArrayList, outcome is always null - firebase

Im working on a quizgame and i want to store some ids in a MutableLiveData-arraylist. Therfore i made a function to loop all my documents in de database and add each ID to the arraylist. BUT the outcome is always null. I don't see where i go wrong?
I'm working with a MVVM-structure
GameViewModel:
class GameViewModel : ViewModel() {
// database instance
val db = FirebaseFirestore.getInstance()
// the current category
private val _category = MutableLiveData<String>()
val category: LiveData<String>
get() = _category
// the list of questionIds of the selected category
private val _questionIdsArray = MutableLiveData<ArrayList<Long>>()
val questionIdsArray: LiveData<ArrayList<Long>>
get() = _questionIdsArray
// the current question
private val _question = MutableLiveData<String>()
val question: LiveData<String>
get() = _question
/**
* Set Current Category
*/
fun SetCategory (categoryName: String){
_category.value = categoryName
}
/**
* Get the list of QuestionIds
*/
fun GetListQuestionIds() {
db.collection("questions")
.whereEqualTo("category", "$_category")
.get()
.addOnSuccessListener { documents ->
for (document in documents) {
_questionIdsArray.value?.add(document.data["questionid"] as Long)
Log.d("GetSize","${_questionIdsArray.value?.size}")
}
Log.d("GetSize2","${_questionIdsArray.value?.size}")
}
.addOnFailureListener { exception ->
Log.w("errorforloop", "Error getting documents: ", exception)
}
}
/**
* Get a Question
*/
fun GetQuizQuestion() {
Log.d("retro","${_questionIdsArray.value?.size}")
db.collection("questions")
.whereEqualTo("category", "$_category")
.whereEqualTo("questionid", "${_questionIdsArray.value?.get(0)}")
.get()
.addOnSuccessListener { documents ->
for (document in documents) {
_question.value = document.data["question"].toString()
}
}
.addOnFailureListener { exception ->
Log.w("err", "Error getting documents: ", exception)
}
}
GAMEFRAGMENT:
class GameFragment : Fragment() {
private lateinit var viewModel: GameViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentGameBinding.inflate(inflater)
// Get the viewModel
viewModel = ViewModelProvider(this).get(GameViewModel::class.java)
binding.lifecycleOwner = this
// Set the viewModel for DataBinding - this allows the bound layout access to all of the data in the VieWModel
binding.gameviewModel = viewModel
//arguments passed
val selectedCategory = arguments?.getString("selectedCategory")!!
//set current category so that the viewModel can use it
viewModel.SetCategory(selectedCategory)
viewModel.GetListQuestionIds()
viewModel.GetQuizQuestion()
return binding.root
}
If someone can enlighten me ...

Your Problem
You're not initializing the array. This is your code:
// the list of questionIds of the selected category
private val _questionIdsArray = MutableLiveData<ArrayList<Long>>()
val questionIdsArray: LiveData<ArrayList<Long>>
get() = _questionIdsArray
This declares a MutableLiveData of type ArrayList<Long>, but does not initialize it so its value defaults to null.
In your for loop you conditionally add items:
_questionIdsArray.value?.add(document.data["questionid"] as Long)
But of course value was never initialized so it's null so add is no-op (does nothing).
The Solution
Just ensure you initialize the live data object at some point.
You could do this inline in the declaration:
// the list of questionIds of the selected category
private val _questionIdsArray = MutableLiveData<ArrayList<Long>>(arrayListOf())
val questionIdsArray: LiveData<ArrayList<Long>>
get() = _questionIdsArray
Or during your attempt to populate the list:
.addOnSuccessListener { documents ->
val idsArray = arrayListOf<Long>() // Non-null list to add to
for (document in documents) {
idsArray.add(document.data["questionid"] as Long)
Log.d("GetSize","${idsArray.size}")
}
_questionIdsArray.value = idsArray // Now set live data with a valid list
Log.d("GetSize2","${_questionIdsArray.value?.size}")
}

Related

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

How to sum of values from Firebase Database in Kotlin?

I have a Firebase Database with several purchases in Android app, points value is assigned for each purchase, then entries are showing by following way:
When userId is log in, a button appears and when user click on it the "showInfoClient" function is called and it must show user and the amount of points. Code is:
private val database = Firebase.database
private val myref = database.getReference("compras")
fun showInfoClient(view: View) {
val userlog = FirebaseAuth.getInstance().currentUser?.displayName
val alertDialogInfo = AlertDialog.Builder(this).create()
myref.orderByChild("userId").equalTo(userlog).addListenerForSingleValueEvent(object :
ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
var sum = 0
for (data in dataSnapshot.children) {
sum += data.child("puntos").getValue(Int::class.java)!!
Toast.makeText(this#Principal, sum, Toast.LENGTH_SHORT).show()
}
}
override fun onCancelled(databaseError: DatabaseError) {}
})
alertDialogInfo.setTitle(userlog)
alertDialogInfo.setMessage("Puntos: ") // + sum
alertDialogInfo.setButton(
AlertDialog.BUTTON_POSITIVE, "OK"
) { dialog, _ ->; dialog.dismiss() }
alertDialogInfo.show()
val btnPositive = alertDialogInfo.getButton(AlertDialog.BUTTON_POSITIVE)
val layoutParams = btnPositive.layoutParams as LinearLayout.LayoutParams
layoutParams.weight = 100f
btnPositive.layoutParams = layoutParams
}
I have tried to use different options but i´m not able to set "sum" value on
alertDialogInfo.setMessage("Puntos: $sum")
Thanks in advance
Any code that needs data from the database needs to be inside onDataChange or be called from there. Code outside of onDataChange will (most likely) run before the data is loaded.
So:
val userlog = FirebaseAuth.getInstance().currentUser?.displayName
val alertDialogInfo = AlertDialog.Builder(this).create()
myref.orderByChild("userId").equalTo(userlog).addListenerForSingleValueEvent(object :
ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
var sum = 0
for (data in dataSnapshot.children) {
sum += data.child("puntos").getValue(Int::class.java)!!
}
alertDialogInfo.setTitle(userlog)
alertDialogInfo.setMessage("Puntos: ") // + sum
alertDialogInfo.setButton(
AlertDialog.BUTTON_POSITIVE, "OK"
) { dialog, _ ->; dialog.dismiss() }
alertDialogInfo.show()
val btnPositive = alertDialogInfo.getButton(AlertDialog.BUTTON_POSITIVE)
val layoutParams = btnPositive.layoutParams as LinearLayout.LayoutParams
layoutParams.weight = 100f
btnPositive.layoutParams = layoutParams
}
override fun onCancelled(databaseError: DatabaseError) {
throw databaseError.toException(); // never ignore errors
}
})
For a longer explanation see:
getContactsFromFirebase() method return an empty list
Setting Singleton property value in Firebase Listener

Async Loading of a TreeView

Hey I am very new to tornadofx struggeling with async loading of data for the treeview. I am loading categories from a rest endpoint, which I want to show in there.
It seems like there's no direct data binding to the children.
when using 'bindChildren' I can provide the observable list, but I have to convert them into Node's. which then would make the populate block kind of obsolete.
What's the recommended way of doing this? I cannot find anything about this.
// Category
interface Category<T : Category<T>> {
val id: String
val name: String
val subcategories: List<T>?
}
//default category:
class DefaultCategory(override val name: String) : Category<DefaultCategory> {
override val id: String = "default"
override val subcategories: List<DefaultCategory>? = null
}
//ViewModel
class CategoryViewModel : ViewModel() {
val sourceProperty = SimpleListProperty<Category<*>>()
fun loadData() {
// load items for treeview into 'newItems'
sourceProperty.value = newItems
}
}
// TreeViewFactoryMethod
private fun createTreeView(
listProperty: SimpleListProperty<Category<*>>
): TreeView<Category<*>> {
return treeview {
root = TreeItem(DefaultCategory("Categories"))
isShowRoot = false
root.isExpanded = true
root.children.forEach { it.isExpanded = true }
cellFormat { text = it.name }
populate { parent ->
when (parent) {
root -> listProperty.value
else -> parent.value.subcategories
}
}
}
}
Assuming that on a button click I call viewmodel.loadData(), I would expect the TreeView to update as soon as there's some new data. (If I would've found a way to bind)
I've never had to use bindChildren for TornadoFX before and your use of async isn't very relevant to what I think is your primary problem. So, admittedly, this question kind of confused me at first but I'm guessing you're just wondering why the list isn't appearing in your TreeView? I've made a test example with changes to make it work.
// Category
interface Category<T : Category<T>> {
val id: String
val name: String
val subcategories: List<T>?
}
//default category:
class DefaultCategory(override val name: String) : Category<DefaultCategory> {
override val id: String = "default"
override val subcategories: List<DefaultCategory>? = null
}
//Just a dummy category
class ChildCategory(override val name: String) : Category<ChildCategory> {
override val id = name
override val subcategories: List<ChildCategory>? = null
}
//ViewModel
class CategoryViewModel : ViewModel() {
//filled with dummy data
val sourceProperty = SimpleListProperty<Category<*>>(listOf(
ChildCategory("Categorya"),
ChildCategory("Categoryb"),
ChildCategory("Categoryc"),
ChildCategory("Categoryd")
).asObservable())
fun loadData() {
sourceProperty.asyncItems {
//items grabbed somehow
listOf(
ChildCategory("Category1"),
ChildCategory("Category2"),
ChildCategory("Category3"),
ChildCategory("Category4")
).asObservable()
}
}
}
class TestView : View() {
val model: CategoryViewModel by inject()
override val root = vbox(10) {
button("Refresh Items").action {
model.loadData()
}
add(createTreeView(model.sourceProperty))
}
// TreeViewFactoryMethod
private fun createTreeView(
listProperty: SimpleListProperty<Category<*>>
): TreeView<Category<*>> {
return treeview {
root = TreeItem(DefaultCategory("Categories"))
isShowRoot = false
root.isExpanded = true
root.children.forEach { it.isExpanded = true }
cellFormat { text = it.name }
populate { parent ->
when (parent) {
root -> listProperty
else -> parent.value.subcategories
}
}
}
}
}
There are 2 important distinctions that are important.
1. The more relevant distinction is that inside the populate block, root -> listProperty is used instead of root.listProperty.value. This will make your list appear. The reason is that a SimpleListProperty is not a list, it holds a list. So, yes, passing in a plain list is perfectly valid (like how you passed in the value of the list property). But now that means the tree view isn't listening to your property, just the list you passed in. With that in mind, I would be considerate over the categories' subcategory lists are implemented as well.
2. Secondly, notice the use of asyncItems in the ViewModel. This will perform whatever task asynchronously, then set the items to list on success. You can even add fail or cancel blocks to it. I'd recommend using this, as long/intensive operations aren't supposed to be performed on the UI thread.

How to deserialize number value in Firestore field and read this inside de while/loop using kotlinx.coroutines

In the firestore I created a field named PararUm, type number (it does not have Int, when I enter it manually) and I put value 1.
The problem is that the return has been PararUm(PararUm=1) and not just 1.
(99-below)
When I resolved this, I would have solved the first part of the project.
Regarding the second, I want to use kotlinx.coroutines to work within a while/loop (which queries the value of the PararUm field) in a synchronous, non-asynchronous way (as firebase requires)
Can I do something like???(999-below):
I threw this topic down, but I was not happy1.
99-below:
model
#IgnoreExtraProperties
data class PararUm(
var PararUm: Int? = 0
)
Activity
var db = FirebaseFirestore.getInstance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var pararumRef =
db.collection("smartmodel").document("xxxxxxxxxxxx")
pararumRef.get().addOnSuccessListener { documentSnapshot ->
var PararUm = documentSnapshot.toObject(PararUm::class.java)
Log.i(ContentValues.TAG, "1999 1999 1999" + PararUm)
}
}
999-below:
while (!FCMotorUmA.value) {
var snapshot = pararumRef.get().await()
    
var pararum = snapshot.toObject(PararUM::class.java)
if (pararum.equals(0)) {
// Do something 1
} else if (pararum.equals(1)) {
// Do something 2
}
}

Kotlin get Field Annotation always empty

I have the following Kotlin Annotation
#Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY_GETTER)
#Retention(AnnotationRetention.RUNTIME)
annotation class Field(val value: String)
And the following Test Code
class TestObject(#field:Field("id") val id: Long) {
#field:Field("string")
val string = "Hello world"
#get:Field("prop")
val prop get() = string
}
class AnnotationTest {
#Test
fun test() {
val obj = TestObject(200L)
for (member in obj::class.declaredMemberProperties) {
if (member.findAnnotation<Field>() != null) {
println(member)
}
println(member)
println(member.annotations)
}
println("#########")
for(member in obj.javaClass.declaredFields) {
println(member)
println(member.annotations)
}
}
}
It will print the following Output:
val TestObject.id: kotlin.Long
[]
val TestObject.prop: kotlin.String
[]
val TestObject.string: kotlin.String
[]
#########
private final java.lang.String TestObject.string
[Ljava.lang.annotation.Annotation;#33d512c1
private final long TestObject.id
[Ljava.lang.annotation.Annotation;#515c6049
Why I can't see the Annotation with Kotlin reflection? Need to find out if the given annotation is present on fields and property getters.
Your annotation for prop is targeting getter, instead of calling findAnnotation on the property, you have to call it on the getter of the property.
for (member in obj::class.declaredMemberProperties) {
if (member.getter.findAnnotation<Field>() != null) { //add .getter
println(member)
}
println(member)
println(member.getter.annotations) //add .getter
}
Your annotation for id and string is targeting field, so what you did in the second loop is correct. Since member.annotations returns Annotation[], you have to change it to a List before printing it.
for(member in obj.javaClass.declaredFields) {
println(member)
println(member.annotations.toList()) //Add .toList()
}
Output:
val TestObject.id: kotlin.Long
[]
val TestObject.prop: kotlin.String
val TestObject.prop: kotlin.String
[#Field(value=[prop])]
val TestObject.string: kotlin.String
[]
#########
private final java.lang.String TestObject.string
[#Field(value=[string])]
private final long TestObject.id
[#Field(value=[id])]

Resources