Firebase with kotlin array of ratings problem - android

I cant get the ratings all at the same time and do the average! Anyone knows how to do this? Get a problem saying "none of the following functions can be called with the arguments supplied"
val ref = FirebaseDatabase.getInstance().getReference("recipes/$id/reci_ratings/")
ref.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(p0: DataSnapshot) {
p0.children.forEach{
val rt= it.child("rating").value
val numbers = arrayOf(rt)
var sum = 0
for (element in numbers) {
sum += element
}
val average = sum / numbers.size
}
}

Welcome to SO. You're more likely to get good answers if you follow these tips: https://stackoverflow.com/help/how-to-ask
But nevertheless...your code seems to lack the closing bracket with parenthesis, but maybe you just missed to add that in your question?
Another thing is that your numbers array will never contain more than one ratings value. You should declare and populate it like so:
val ref = FirebaseDatabase.getInstance().getReference("recipes/$id/reci_ratings/")
var numbers: ArrayList<Int> = arrayListOf() // Change to whatever type is accurate in your case
var sum = 0
ref.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(p0: DataSnapshot) {
p0.children.forEach {
val rt = it.child("rating").value
numbers.add(rt)
}
// After the forEach loop is finished you should have all the ratings in the numbers array
}
})

Related

LiveData in Nested RecyclerView

The fragment I am coding right now is supposed to give the user a calendaric overview of his meal planning schedule. So via date picker, he can choose a time period and the program will show the user which recipes he has chosen for the chosen weekdays.
So I build a nested RecyclerView with the weekdays as parent layer and corresponding recipes as a child layer. The data class for the weekday layer looks like this :
data class Weekday (
val weekday : String,
val listWithRecipes : List<Recipe>?
)
The class for the Recipe entity looks like this:
#Entity(tableName = "Recipe")
#Parcelize
data class Recipe(
#PrimaryKey var recipeName : String,
var description : String?,
var serving : Int,
var preparationTime : Int?
) : Parcelable
The Adapter for the top Recycler View like this :
class MealPlanAdapter(private var mealplan: List<Weekday>) :
RecyclerView.Adapter<MealPlanAdapter.MealPlanViewHolder>(), RecipeAdapter.OnItemClickListener {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MealPlanViewHolder {
return MealPlanViewHolder(
DailyMealplanItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun getItemCount() = mealplan.size
override fun onBindViewHolder(holder: MealPlanViewHolder, position: Int) {
val weekday = mealplan[position]
val recipeAdapter = RecipeAdapter(this)
recipeAdapter.submitList(weekday?.listWithRecipes)
holder.dayOfWeek.text = weekday.weekday
val recipeLayoutManager = LinearLayoutManager(holder.recyclerView.context,RecyclerView.VERTICAL, false)
recipeLayoutManager.initialPrefetchItemCount = 4
holder.recyclerView.apply{
layoutManager = recipeLayoutManager
adapter = recipeAdapter
}
}
fun setSchedule(mealplan : List <Weekday>){
this.mealplan = mealplan
notifyDataSetChanged()
}
inner class MealPlanViewHolder(val binding: DailyMealplanItemBinding) :
RecyclerView.ViewHolder(binding.root) {
val recyclerView: RecyclerView = binding.rvRecyclerView
val dayOfWeek: TextView = binding.tvDayOfWeek
}
override fun onItemClick(recipe: Recipe) {
TODO("Not yet implemented")
}
}
Whenever the user changes the time period, the setScheduled() method in the adapter gets called in the fragment.
materialDatePickerStartDate.addOnPositiveButtonClickListener(
MaterialPickerOnPositiveButtonClickListener<Any?> { selection ->
_binding.viewmodel!!.startDateInUTCFormat = selection as Long
_binding.tvStartDate.setText(materialDatePickerStartDate.headerText)
adapter.setSchedule(
_binding.viewmodel!!.returnListWithWeekDaysAndCorrespondingRecipes(
_binding.viewmodel!!.startDateInUTCFormat,
_binding.viewmodel!!.endDateInUTCFormat
)
)
}
)
The viewmodel looks like this :
#HiltViewModel
class MealplanViewModel #Inject constructor(
val mealPlanRepository: MealPlanRepository
) : ViewModel() {
private lateinit var _binding: FragmentMealPlanBinding
var startDateInUTCFormat: Long = System.currentTimeMillis()
var endDateInUTCFormat: Long = System.currentTimeMillis()
fun returnListWithWeekDaysAndCorrespondingRecipes(
startDate: Long,
endDate: Long
): ArrayList<Weekday> {
var startDate = Date(startDateInUTCFormat)
var endDate = Date(endDateInUTCFormat)
var startDateCalendar = dateToCalendar(startDate)
var endDateCalendar = dateToCalendar(endDate)
val calendarDays = createListWithCalendarDates(startDateCalendar, endDateCalendar)
return createListWithWeekDaysAndCorrespondingRecipes(calendarDays)
}
fun dateToCalendar(date: Date): Calendar {
var calInstance = Calendar.getInstance()
calInstance.setTime(date)
return calInstance
}
fun createListWithCalendarDates(
startDateCalendar: Calendar,
endDateCalendar: Calendar
): ArrayList<Calendar> {
var listWithCalendarDates = arrayListOf<Calendar>()
while (startDateCalendar <= endDateCalendar) {
listWithCalendarDates.add(startDateCalendar.clone() as Calendar)
startDateCalendar.add(Calendar.DATE, 1)
}
return listWithCalendarDates
}
fun createListWithWeekDaysAndCorrespondingRecipes(calendarDays: ArrayList<Calendar>): ArrayList<Weekday> {
var dayOfWeekAsString: String
var listWithDaysOfWeeksAndRecipes = arrayListOf<Weekday>()
var flattenedListWithRecipes: List<Recipe>?
for (i in 0 until calendarDays.size) {
var dayOfWeekAsInt = calendarDays[i].get(Calendar.DAY_OF_WEEK)
dayOfWeekAsString = when (dayOfWeekAsInt) {
1 -> "Sunday"
2 -> "Monday"
3 -> "Tuesday"
4 -> "Wednesday"
5 -> "Thursday"
6 -> "Friday"
else -> "Saturday"
}
var calendarDateInString =
transformCalendarDateIntoRequiredStringFormat(calendarDays[i])
var listWithDateAndCorrespondingRecipes: List<MealplanScheduleWithRecipes> =
listOf()
var liveDatalistWithDateAndCorrespondingRecipes =
mealPlanRepository.getMealplanScheduleWithRecipes(calendarDateInString)
liveDatalistWithDateAndCorrespondingRecipes.observeForever() { list ->
listWithDateAndCorrespondingRecipes = list
var listWithRecipes = listWithDateAndCorrespondingRecipes?.map { it.recipes }
flattenedListWithRecipes = listWithRecipes?.flatten()
var wochentag = dayOfWeekAsString
listWithDaysOfWeeksAndRecipes.add(Weekday(dayOfWeekAsString, flattenedListWithRecipes))
}
}
return listWithDaysOfWeeksAndRecipes
}
fun transformCalendarDateIntoRequiredStringFormat(calendarDate: Calendar): String {
var year = calendarDate.get(Calendar.YEAR)
var month = transformCalendarMonthFormatToCorrectMonth(calendarDate)
var day = calendarDate.get(Calendar.DAY_OF_MONTH)
return "$day" + "$month" + "$year"
}
fun transformCalendarMonthFormatToCorrectMonth(calendarDate: Calendar): String {
var monthCalendarFormat = calendarDate.get(Calendar.MONTH)
var monthCorrectFormat = when (monthCalendarFormat) {
0 -> "1"
1 -> "2"
2 -> "3"
3 -> "4"
4 -> "5"
5 -> "6"
6 -> "7"
7 -> "8"
8 -> "9"
9 -> "10"
10 -> "11"
else -> "12"
}
return monthCorrectFormat
}
fun datesAreReasonable(startDate: Long, endDate: Long): Boolean {
return (startDate <= endDate)
}
}
My problem is the list that is passed to the RecyclerView Adapter consists of Weekday objects, which consist of the name of the weekday and the corresponding recipes (see data class "weekday" on top).
In the method "createListWithWeekDaysAndCorrespondingRecipes" in the viewmodel I create this list in a for loop that gets all weekdays between given Dates and their corresponding recipes. However, the recipes are LiveData fetched asynchronously via Room database query while the names of the weekdays are derived synchronously in the main thread. At the end however when I create the Weekday object
(see listWithDaysOfWeeksAndRecipes.add(Weekday(dayOfWeekAsString, flattenedListWithRecipes) at the end of the for loop) I need them together at the same time. I haven't found a way how I can coordinate this successfully. At the moment the logics for adding the object to the list is in the asynchronous "observeForever" block.
See here:
liveDatalistWithDateAndCorrespondingRecipes.observeForever() { list ->
listWithDateAndCorrespondingRecipes = list
var listWithRecipes = listWithDateAndCorrespondingRecipes?.map { it.recipes }
flattenedListWithRecipes = listWithRecipes?.flatten()
var wochentag = dayOfWeekAsString
listWithDaysOfWeeksAndRecipes.add(Weekday(dayOfWeekAsString, flattenedListWithRecipes))
}
This creates wrong results, probably because the coordination between main thread and the observer thread doesn't work.
If I however take the logics of adding out of the observer block, the list with recipes will give me null, because of the asynchronous character of the query.
I know that I described the problem very badly. Maybe still someone got a grasp of it and can help?
You should try to avoid using observeForever, I expect you are using this inside a fragment or an activity which actually has a lifecyclescope that your observer can use.
Your observer should look something like this
liveDataList.observe(viewLifecycleOwner, { list ->
// The way I do it at the moment I just set the recyclerViews adapter and layoutManager here
// This is not the best way to do it, so please keep that in mind
recyclerView.apply {
adapter = MyAdapter(list)
layoutManager = LinearLayoutManager(requireContext())
}
})
// Or if used inside an activity
liveDataList.observe(this, {})
This way your observer will be attached to your lifecycle and "die" together with your view. Whenever that list changes, you will show all entities in the recyclerView. HOWEVER, when you use an Array together with LiveData the LiveData object never "updates" when you just add something to that value, since the array only is a memory reference to the start of the array.
To counter this whenever you add something to your array you need to refresh the LiveData object in order to trigger an update and all observers.
myLiveDataObject.value.add(someOtherObject)
myLiveDataObject.value = myLiveDataObject.value
myLiveDataObject.value = myLiveDataObject.value triggers all observers that there has been a change, annoying I know
If you use it inside a viewHolder or adapter simply pass the lifecycle along with the list
I am also quite new to kotlin, keep that in mind and I guarantee you there is a better way to do this, but hope it helps

How to update object value in MutableList?

I have a MutableList in my Android project where i'm adding an object called Articolo, then when a new item is added to that list i need to check if one item with same ID exist and if it does i need to update it's quantity.
The issue is that i'm trying to use MutableList.find to find the object with the same ID and when i find it i'm simply add the quantity to existing quantity but instead it remains immutable.
Here is my Articolo.kt
data class Articolo(var barcode: String, var qta: Int) {
constructor() : this ("", 0)
}
And here is my function where i'm adding data to MutableList
private var articoli = mutableListOf<Articolo>()
private fun addBarcode(barcode: String, qta: Int) {
if (barcode.isEmpty()) {
txtBarcode.requestFocus()
return;
}
articoli.find{
it.barcode == barcode
}?.qta?.plus(qta) ?:
articoli.add(Articolo(barcode, qta))
}
So if i add the first object like barcode: 1111, qty: 1 and then another same object instead of having one element array with qty 2 i still have qty 1..
That's because .plus(Int) returns a new value. You're not changing the property.
Instead you should do:
fun addBarcode(barcode: String, qta: Int) {
val existant = articoli.find { it.barcode == barcode }
if (existant != null) existant.qta += qta
else articoli.add(Articolo(barcode, qta))
}
#VaiTon86 has the answer (you're not actually changing the value in the Articolo object) but really, you should probably be using a Map here anyway:
maximum one of each item
lookup by some value (barcode)
that's a map!
There's a few ways you could implement it, here's one:
val articoli = mutableMapOf<String, Articolo>()
private fun addBarcode(barcode: String, qta: Int) {
articoli.getOrPut(barcode) { Articolo(barcode, 0) }
.let { it.qta += qta }
}
So the getOrPut just adds a new zero-quantity Articolo entry if there isn't already one, and then you add qta to what's already there for that entry.

RecycleView sorting

For some time I have been struggling with the problem of sorting information in RecycleView, the data comes from Firebase, I would like to sort single row depending on one of the objects from a class. maybe the code will explain it better.
private fun fetchHours(){
var link = FirebaseAuth.getInstance().uid
val ref = FirebaseDatabase.getInstance().getReference("/WorkTime/$link")
ref.addListenerForSingleValueEvent(object: ValueEventListener{
override fun onDataChange(p0: DataSnapshot) {
val adapter = GroupAdapter<GroupieViewHolder>()
p0.children.forEach {
Log.d("Vievactivity", it.toString())
var singlerow = it.getValue(CounterActivity.WorkTimeCountedClass::class.java)
if (singlerow != null ){
adapter.add(WorkTimeList(singlerow))
}
}
recycleViev_list.adapter = adapter
adapter.setOnItemClickListener{ item, view ->
val useruuid = item as WorkTimeList
val intent = Intent(view.context, EditDataActivity::class.java)
intent.putExtra(CLICKED_ITEM_KEY, useruuid.naz.uuid)
startActivity(intent)
}
}
override fun onCancelled(p0: DatabaseError) {
}
})
}
}
class WorkTimeList(var naz: CounterActivity.WorkTimeCountedClass): Item<GroupieViewHolder>(){
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.itemView.row_viewactivity_invisible_uuid.text = naz.uuid
viewHolder.itemView.Row_viewactivity_employer.text = naz.employer
viewHolder.itemView.Row_viewactivity_StartWork.text = naz.startTimeString
viewHolder.itemView.Row_viewactivity_StopWork.text = naz.stopTimeString
viewHolder.itemView.imageView_planTime.text = naz.planedTimeToSpendAtWorkString
viewHolder.itemView.imageView_complitedTime.text = naz.totalTimeSpendAtworkString
viewHolder.itemView.imageView_overHours.text = naz.overHoursString
}
override fun getLayout(): Int {
return R.layout.row_viewactivity
}
}
as a result, he gets something like that
screen
I would like individual rows to be sorted depending on user preference, for example ascending from the variable viewHolder.itemView.Row_viewactivity_StartPracy.text = naz.startTimeString or descending from viewHolder.itemView.imageView_complitedTime.text = name. TotalTime Does anyone have an idea how to solve this problem?
val ref = FirebaseDatabase.getInstance().getReference("/WorkTime/$link")
ref.orderByChild("order").addListenerForSingleValueEvent(object: ValueEventListener{
override fun onDataChange(p0: DataSnapshot) {
val adapter = GroupAdapter<GroupieViewHolder>()
p0.children.forEach {
Log.d("Vievactivity", it.toString())
var singlerow = it.getValue(CounterActivity.WorkTimeCountedClass::class.java)
if (singlerow != null ){
adapter.add(WorkTimeList(singlerow))
}
}
recycleViev_lista.adapter = adapter
I found a solution, maybe just writing the first question here inspired me :)
So using "orderByChild" I added sorting, unfortunately only increasing sorting works. To change this I came up with the idea that the value after which I want to sort subtract from 0 - it will reverse the sorting method :)
I am not good at explaining, so maybe for example:
Writes to the Firebase start time in milliseconds for example (1579097163159), (1578374795583), (1579152252463) - sorting this data we get the result in increasing
1 - (1578374795583)
2 - (1579097163159)
3 - (1579152252463)
Unfortunately, I did not find how to change orderByChaild tabs so that they sort in descending order ...... we can subtract from these values ​​and get the expected effect:
1 - (1578374795583) -> 0 - 1578374795583 = - 1578374795583
2 - (1579097163159) -> 0 - 1579097163159 = - 1579097163159
3 - (1579152252463) -> 0 - 1579152252463 = - 1579152252463
We need to add a variable to Firebase, which will be the result of this operation, we then sort by the result of this operation and it's ready.
1 - 1579152252463
2 - 1579097163159
3 - 1578374795583
I hope I helped someone as much as I can't count how many times I found help here. Regards

Questions about using 'liveData.observe'

I'm learning Android development on my own. I thought I had gotten my head around what/how 'liveData.observe' worked. But...
Making a small simple app that listens for input from a Bluetooth device and will display or hide an image based on that bluetooth input.
I have all that working, but now I want to add another feature and i'm running into a wall. The feature is to increase a counter each time the liveData.Observe returns TRUE.
Either I don't understand 'liveData.observe' or i'm trying to use it incorrectly(it is probably both of these things).
I'm working on a function now to increase this counter and this is the spot i'm getting hung up on. My current thinking is to create a seperate function(pigCounter()). But I'm getting nowhere.
onCreate
class MainActivity : AppCompatActivity() {
private var liveData: MutableLiveData<String> = MutableLiveData()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
<SNIP>
...
</SNIP>
liveData.observe(this, androidx.lifecycle.Observer {
imageView_mothership_LED_state.showOrHideImage(it == "1")
})
pigCounter()
}
Below is from activity_main.xml
imageView_mothership_LED_state
<ImageView
android:id="#+id/imageView_mothership_LED_state"
android:layout_width="124dp"
android:layout_height="144dp"
android:scaleType="fitCenter"
android:soundEffectsEnabled="false"
app:layout_constraintBottom_toBottomOf="#+id/imageView_blueYesOrNo"
app:layout_constraintEnd_toEndOf="#+id/imageView_blueYesOrNo"
app:layout_constraintStart_toStartOf="#+id/imageView_blueYesOrNo"
app:layout_constraintTop_toTopOf="#+id/imageView_blueYesOrNo"
app:srcCompat="#drawable/ic_check_black_24dp"
android:contentDescription="Check mark image to indicate if LED is on/off" />
Below is the function that sets the images visibility
showOrHideImage()
// display or don't display check mark image
private fun View.showOrHideImage(imageShow: Boolean) {
visibility = if (imageShow) View.VISIBLE else View.GONE
}
This is the function that handles the incoming bluetooth data.
readBlueToothDataFromMothership()
private fun readBlueToothDataFromMothership(bluetoothSocket: BluetoothSocket) {
Log.i(LOGTAG, Thread.currentThread().name)
val bluetoothSocketInputStream = bluetoothSocket.inputStream
val buffer = ByteArray(1024)
var bytes: Int
//Loop to listen for received bluetooth messages
while (true) {
try {
bytes = bluetoothSocketInputStream.read(buffer)
val readMessage = String(buffer, 0, bytes)
liveData.postValue(readMessage)
} catch (e: IOException) {
e.printStackTrace()
break
}
}
This is the function i'm writing, in that I want to increase a counter each time.
pigCount
private fun pigCount() {
var count = 0
var counterTextView = findViewById<TextView>(R.id.textView_blueCounter)
if(imageView_mothership_LED_state.showOrHideImage(true)) {
counterTextView.text = count.toString()
count++
}
This does not work due to a type mismatch(expecting a Boolean, getting Unit). I've also tried moving this into the liveData.observe function up in onCreate(). But same road block.
Can anyone point me in the right direction. I have a sneaky feeling I am way off on this. And would appreicate a nudge. :)
To count how many times a LiveData returns true it is really easy, you could just use a MediatorLiveData.
Assuming you have a proper LiveData<Boolean>, you could do as follows:
val trueFalse = MutableLiveData<Boolean>()
val counter by lazy {
MediatorLiveData<Int>().apply {
value = 0 // Initialize the counter
// Add the trueFalse as a source of this live data
addSource(trueFalse) { boolVal ->
if(boolVal == true) {
// If the mediator live data had a value, use it, if null use 0; and add +1 to it because the boolVal was true
value = (value ?: 0) + 1
}
}
}
}
/* Somewhere else in the code */
fun setupTextView() {
counter.observe({lifecycle}) {
val tv = findViewById<TextView>(R.id.tv_something)
tv.text = "$it"
}
}
This creates a MediatorLiveData that will have as a source the trueFalse live data.
If the value is true will proceed and post a new value in the counter live data.

How to insert items in list of item without a lot of iterations

I have a dataClass, which contains a unique code of item, code of the parent and two lists - categories and subcategories.
data class MyItem (
var code: String,
var name: String,*
var data: String,
var count: Int,
var parent: String,
var categories: MutableList<MyItem>,
var subcategories: MutableList<MyItem>
)
I've got from server 3 different items list. And structure that I want to get is:
- listOfTopLevelItems
--- listOfMiddleLevelItems
----- listOfBottomLevelItems
where every topLevelItem contains a list of middleLevelItems and every middle level items contains a list of bottom level items. For that i used code below
for (topItem in topLevelItems) {
for (middleItem in middleLevelItems) {
if (topItem.code == middleItem.parent) {
val middleResultItem = middleItem
for (bottomItem in bottomLevelItems) {
if (middleItem.code == bottomItem.parent) {
middleResultItem.subcategories.add(bottomItem)
}
}
topItem.categories.add(middleResultItem)
}
}
result.add(topItem)
}
But the problem is if i will have a lots of items on bottom level, than it will be a lot of iterations. Is there is another way to solve this?
So what you have is a DAG of depth 3. I am going to make some other adjustments other than just solving your iteration problem.
First, I think the structure of your data classes is a bit redundant for describing a graph of objects. You do not need the category and subcategory fields in my opinion. Stripping out the irrelevant fields, this is what mine would look like:
data class MyItem(
var code: String,
var parent: String? = null,
var categories: MutableList<MyItem> = mutableListOf()
){
val subcategories: List<MyItem>
get() = categories.flatMap { it.categories }
}
A root/top item will be any item where the parent is null. And then its categories are its immediate children, and its sub categories are its grandchildren. I have provided a property here which will take care of grandchildren if you really want that accessor, and it means if you add something to a child, the parents grandchildren will be updated automatically :D.
Now for version 1 of creating the object graph. This keeps things in line with your apparent structure of knowing which ones are roots, children and grand children. But this is not needed as you will see in version 2.
fun main() {
val topItems = listOf(MyItem("1"), MyItem("2"))
val middleItems = listOf(MyItem("1_1", "1"), MyItem("1_2", "1"), MyItem("2_1", "2"))
val bottomItems = listOf(MyItem("1_1_1", "1_1"), MyItem("1_2_1", "1_2"), MyItem("2_1_1", "2_1"))
val topByID = topItems.map { it.code to it }.toMap()
val middleByID = middleItems.map { it.code to it }.toMap()
bottomItems.forEach { middleByID[it.parent]?.categories?.add(it) }
middleItems.forEach { topByID[it.parent]?.categories?.add(it) }
println(topItems)
println(topItems[0].subcategories)
}
But really, all you need to know for building an obect graph is parent child relationship, and they can all just be in a big collection. Then you can rebuild your object graph like this:
fun main() {
val topItems = listOf(MyItem("1", "*"), MyItem("2", "*"))
val middleItems = listOf(MyItem("1_1", "1"), MyItem("1_2", "1"), MyItem("2_1", "2"))
val bottomItems = listOf(MyItem("1_1_1", "1_1"), MyItem("1_2_1", "1_2"), MyItem("2_1_1", "2_1"))
val allItems = topItems + middleItems + bottomItems
val allItemsByID = allItems.map { it.code to it }.toMap()
allItems.forEach {
allItemsByID[it.parent]?.categories?.add(it)
}
println(topItems)
println(topItems[0].subcategories)
}
This is my favorite approach :D

Categories

Resources