In this shopping app, I try to set/get ratings for the product using Firebase firestore db, The problem is happening when I try to set a product rating, firstly I have the following fields in this path >>> /PRODUCTS/PRODUCT_ID_XYZ/
1_star (number)
2_star (number)
3_star (number)
4_star (number)
5_star (number)
average_rating (String)
total_ratings (String)
and this is the rating layout, that contains the basic rating bar made by me
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="@color/white"
android:elevation="3dp">
<TextView
android:id="@+id/ratings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:fontFamily="@font/arial"
android:text="Ratings"
android:textColor="@color/black"
android:textSize="@dimen/_20ssp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/productRatingContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ratings">
<TextView
android:id="@+id/averageRatingTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/_2sdp"
android:layout_marginEnd="@dimen/_2sdp"
android:fontFamily="@font/arial"
android:text="4.5"
android:textColor="@color/black"
android:textSize="@dimen/_36ssp"
android:textStyle="bold">
</TextView>
<ImageView
android:id="@+id/averageRatingStar"
android:layout_width="@dimen/_30sdp"
android:layout_height="@dimen/_30sdp"
android:layout_gravity="center"
android:layout_margin="8dp"
android:src="@drawable/star"
app:tint="@color/black">
</ImageView>
</LinearLayout>
<TextView
android:id="@+id/secondTotalRatingsTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:text="27 ratings"
android:textSize="@dimen/_14ssp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/productRatingContainer" />
<LinearLayout
android:id="@+id/ratingProgressBarContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="@+id/productRatingContainer"
app:layout_constraintStart_toStartOf="@+id/productRatingContainer"
app:layout_constraintTop_toBottomOf="@+id/secondTotalRatingsTV">
<ProgressBar
android:id="@+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:layout_weight="1"
android:max="100"
android:progress="80"
android:progressTint="#00AC06" />
<ProgressBar
android:id="@+id/progressBar2"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:layout_weight="1"
android:max="100"
android:progress="60"
android:progressTint="#00AC06" />
<ProgressBar
android:id="@+id/progressBar3"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:layout_weight="1"
android:max="100"
android:progress="40"
android:progressTint="#E4CD00" />
<ProgressBar
android:id="@+id/progressBar4"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:layout_weight="1"
android:max="100"
android:progress="30"
android:progressTint="#E4CD00" />
<ProgressBar
android:id="@+id/progressBar5"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:layout_weight="1"
android:max="100"
android:progress="10"
android:progressTint="#ff0000" />
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout6"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginEnd="8dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="@+id/ratingProgressBarContainer"
app:layout_constraintEnd_toStartOf="@+id/ratingProgressBarContainer"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/ratingProgressBarContainer">
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_gravity="end"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:padding="1dp"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/ratingMiniViewTV1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/arial"
android:text="5"
android:textColor="@color/black"
android:textSize="@dimen/_8ssp"
android:textStyle="bold">
</TextView>
<ImageView
android:id="@+id/ratingMiniStar1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="2dp"
android:src="@drawable/star"
app:tint="@color/black">
</ImageView>
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_gravity="end"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:padding="1dp"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/productRatingMiniViewTV2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/arial"
android:text="4"
android:textColor="@color/black"
android:textSize="@dimen/_8ssp"
android:textStyle="bold">
</TextView>
<ImageView
android:id="@+id/ratingMiniStar2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="2dp"
android:src="@drawable/star"
app:tint="@color/black">
</ImageView>
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout3"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_gravity="end"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:padding="1dp"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/productRatingMiniViewTV3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/arial"
android:text="3"
android:textColor="@color/black"
android:textSize="@dimen/_8ssp"
android:textStyle="bold">
</TextView>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="2dp"
android:src="@drawable/star"
app:tint="@color/black">
</ImageView>
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout4"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_gravity="end"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal"
android:padding="1dp"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/productRatingMiniViewTV4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/arial"
android:text="2"
android:textColor="@color/black"
android:textSize="@dimen/_8ssp"
android:textStyle="bold">
</TextView>
<ImageView
android:id="@+id/ratingMiniStar4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="2dp"
android:src="@drawable/star"
app:tint="@color/black">
</ImageView>
</LinearLayout>
<LinearLayout
android:id="@+id/linearLayout5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:gravity="center"
android:orientation="horizontal"
android:padding="1dp"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/productRatingMiniViewTV5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/arial"
android:text="1"
android:textColor="@color/black"
android:textSize="@dimen/_8ssp"
android:textStyle="bold">
</TextView>
<ImageView
android:id="@+id/ratingMiniStar5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="2dp"
android:layout_weight="1"
android:src="@drawable/star"
app:tint="@color/black">
</ImageView>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/ratingNumbersContainer"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginStart="6dp"
android:orientation="vertical"
android:paddingStart="8dp"
android:paddingEnd="8dp"
app:layout_constraintBottom_toBottomOf="@+id/ratingProgressBarContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/ratingProgressBarContainer"
app:layout_constraintTop_toTopOf="@+id/ratingProgressBarContainer">
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:text="9"
android:textAlignment="center"
android:textColor="#727272"
android:textSize="@dimen/_10ssp" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:text="5"
android:textAlignment="center"
android:textColor="#727272"
android:textSize="@dimen/_10ssp" />
<TextView
android:id="@+id/textView3"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="7"
android:textAlignment="center"
android:textColor="#727272"
android:textSize="@dimen/_10ssp" />
<TextView
android:id="@+id/textView4"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="10"
android:textAlignment="center"
android:textColor="#727272"
android:textSize="@dimen/_10ssp" />
<TextView
android:id="@+id/textView5"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:text="2"
android:textAlignment="center"
android:textColor="#727272"
android:textSize="@dimen/_10ssp" />
</LinearLayout>
<View
android:id="@+id/divider5"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="#707070"
app:layout_constraintEnd_toEndOf="@+id/ratingNumbersContainer"
app:layout_constraintStart_toStartOf="@+id/ratingNumbersContainer"
app:layout_constraintTop_toBottomOf="@+id/ratingNumbersContainer" />
<TextView
android:id="@+id/totalRatingsFigure"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="27"
android:textColor="#727272"
app:layout_constraintEnd_toEndOf="@+id/ratingNumbersContainer"
app:layout_constraintStart_toStartOf="@+id/ratingNumbersContainer"
app:layout_constraintTop_toBottomOf="@+id/ratingNumbersContainer" />
<TextView
android:id="@+id/textView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:fontFamily="@font/arial"
android:text="your ratings"
android:textColor="#727272"
android:textSize="@dimen/_12ssp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout6" />
<LinearLayout
android:id="@+id/rateNowContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="64dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView6">
<ImageView
android:id="@+id/starIV01"
android:layout_width="0dp"
android:layout_height="@dimen/_20sdp"
android:layout_weight="1"
app:srcCompat="@drawable/star"
app:tint="@color/mediumGray" />
<ImageView
android:id="@+id/starIV02"
android:layout_width="0dp"
android:layout_height="@dimen/_20sdp"
android:layout_weight="1"
app:srcCompat="@drawable/star"
app:tint="@color/mediumGray" />
<ImageView
android:id="@+id/starIV03"
android:layout_width="0dp"
android:layout_height="@dimen/_20sdp"
android:layout_weight="1"
app:srcCompat="@drawable/star"
app:tint="@color/mediumGray" />
<ImageView
android:id="@+id/starIV04"
android:layout_width="0dp"
android:layout_height="@dimen/_20sdp"
android:layout_weight="1"
app:srcCompat="@drawable/star"
app:tint="@color/mediumGray" />
<ImageView
android:id="@+id/starIV05"
android:layout_width="0dp"
android:layout_height="@dimen/_20sdp"
android:layout_weight="1"
app:srcCompat="@drawable/star"
app:tint="@color/mediumGray" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
the setting rate method in my repo
interface ProductDetailsRepo {
suspend fun setRating(
productId: String,
starPosition: Long,
whatStar:Long,
averageRating:Long,
totalRatings:Long,
myRatingIds: ArrayList<String>,
myRating: ArrayList<Long>
) :Flow<Resource<Boolean>>
}
and the implementation ProductDetailsRepoImpl
class ProductDetailsRepoImpl @Inject constructor(
private val firestore: FirebaseFirestore,
private val firebaseAuth: FirebaseAuth
) : ProductDetailsRepo {
override suspend fun setRating(
productId: String,
starPosition: Long,
whatStar: Long,
averageRating: Long,
totalRatings: Long,
myRatingIds: ArrayList<String>,
myRating: ArrayList<Long>
): Flow<Resource<Boolean>> {
val result = MutableStateFlow<Resource<Boolean>>(Resource.Ideal())
result.value = Resource.Loading()
val productRating: HashMap<String, Any> = HashMap()
productRating[(starPosition + 1).toString() + "_star"] = whatStar
productRating["average_rating"] = averageRating
productRating["total_ratings"] = totalRatings
firestore.collection("PRODUCTS")
.document(productId)
.update(productRating)
.addOnSuccessListener {
val rating: HashMap<String, Any> = HashMap()
rating["list_size"] = myRatingIds.size.toLong() + 1
rating["product_id_" + myRating.size] = productId
rating["rating_" + myRatingIds.size] = starPosition.toLong() + 1
firestore.collection(
"USERS"
).document(firebaseAuth.currentUser?.uid!!)
.collection("USER_DATA")
.document("MY_RATINGS")
.update(rating).addOnSuccessListener {
result.value = Resource.Success(true)
}.addOnFailureListener {
result.value = Resource.Error(it.message.toString())
}
}.addOnFailureListener {
result.value = Resource.Error(it.message.toString())
}
return result
}
}
viewModel layer
@HiltViewModel
class ProductDetailsViewModel @Inject constructor(
private val setRatingUseCase: SetRatingUseCase ) :
ViewModel() {
private var _setRatingState = MutableStateFlow<Resource<Boolean>>(Resource.Ideal())
val setRatingState: Flow<Resource<Boolean>> get() = _setRatingState
fun setRatings(
productId: String,
starPosition: Long,
whatStar: Long,
averageRating: Long,
totalRatings: Long,
myRatingIds: ArrayList<String>,
myRating: ArrayList<Long>
) {
viewModelScope.launch {
setRatingUseCase(
productId,
starPosition,
whatStar,
averageRating,
totalRatings,
myRatingIds,
myRating
).collect {
_setRatingState.emit(it)
}
}
}
}
and this the UI/Fragment part and I think the problem happens here, the expected scenario is it should loop on rateNowContainer.childCount
which is 5 stars and set the click listener for each star according to starPosition , so when the user clicks on the first star the rating UI elements (like progressbar and textview) updated to 1 star, when user click on 2 stars it's updated to two stars and so on..., but the problem is when I click on any star it's updated as 5 star, I tried to log the starPosition before I click on any star and I see that the for working immediately and the count start from position 4 which is (5 star) but I didn't know how to fix it
//========= Rating layout
for (i in 1 until binding.ratingsLayout.rateNowContainer.childCount) {
Log.d(TAG, "index: $i")
starPosition = i.toLong()
Log.d(TAG, "starPosition: $starPosition")
binding.ratingsLayout.rateNowContainer.getChildAt(i).setOnClickListener { view ->
if (signOutViewModel.firebaseAuth.currentUser == null) {
Constants.signInSignUpDialog(
requireContext(),
R.id.productDetailsFragment,
layoutInflater,
requireView()
)
} else {
setRating(i.toLong())
if (myRatingsIds.contains(productId)) {
} else {
Log.d(TAG, "starPosition: $starPosition")
productDetailsViewModel.setRatings(
productId, starPosition,
whatStar = starPosition +1,
averageRating = calculateAverageRating(starPosition + 1),
totalRatings = documentSnapshot["total_ratings"] as Long + 1,
myRatingsIds,
myRatings
)
}
}
}
}
lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED){
productDetailsViewModel.setRatingState.collect{ response->
if(response is Resource.Success){
myRatingsIds.add(productId)
myRatings.add(starPosition+1)
val ratingTV = binding.ratingsLayout
.ratingNumbersContainer.getChildAt( (5 - starPosition - 1).toInt()) as TextView
ratingTV.text = (ratingTV.text.toString().toInt() + 1).toString()
binding.productImageViewPager.productTotalRatingMiniViewTV.text =
"(" + documentSnapshot["total_ratings"] as Long + 1 + ") Ratings"
binding.ratingsLayout.secondTotalRatingsTV.text =
(documentSnapshot["total_ratings"] as Long + 1).toString() + " ratings"
binding.ratingsLayout.totalRatingsFigure.text =
(documentSnapshot["total_ratings"] as Long + 1).toString()
binding.ratingsLayout.averageRatingTV.text = calculateAverageRating(starPosition + 1)
.toString()
binding.productImageViewPager.averageRatingMiniViewTV.text =
calculateAverageRating(
starPosition + 1
).toString()
for (x in 0..4) {
val ratingFigures =
binding.ratingsLayout.ratingNumbersContainer.getChildAt(x) as TextView
val progressBar =
binding.ratingsLayout.ratingProgressBarContainer.getChildAt(x) as ProgressBar
val maxProgress =
Integer.valueOf((documentSnapshot["total_ratings"] as Long + 1).toString())
//
progressBar.max = maxProgress
progressBar.progress = ratingFigures.text.toString()
.toInt()
}
Toast.makeText(requireContext(), "Thanks for rating", Toast.LENGTH_SHORT).show()
}else if(response is Resource.Error){
setRating(initialRating)
Log.e(TAG, "setRatingState: ${response.message.toString()}")
}
}
}
}
this image is for the rating layout
It seems that you are using a for loop in Kotlin to iterate through the children of a view and set their click listeners. You are wondering why the for loop starts immediately and the star position is changed immediately, and how to prevent this.
According to the Kotlin documentation, a for loop iterates through anything that provides an iterator, such as a range, an array, a string, or a collection. In your case, you are using the until keyword to create a range from 1 until the child count of the view. This means that the loop will start from 1 and end at the child count minus one.
The loop will execute the body for each value in the range, which means that it will set the click listener for each child view and assign the star position to the current index. This happens when the loop is executed, not when the user clicks on the view.
If you want to prevent this, you need to move the logic that depends on the user’s click inside the click listener, and also you can use the indexOfChild method of the view group to get the index of a specific child view. For example, you can do something like this: For example, you can do something like this:
for (i in 1 until binding.ratingsLayout.rateNowContainer.childCount) {
Log.d(TAG, "index: $i")
binding.ratingsLayout.rateNowContainer.getChildAt(i).setOnClickListener { view ->
// get the star position of the view's
starPosition = binding.ratingsLayout.rateNowContainer.indexOfChild(view).toLong()
Log.d(TAG, "starPosition: $starPosition")
if (signOutViewModel.firebaseAuth.currentUser == null) {
Constants.signInSignUpDialog(
requireContext(),
R.id.productDetailsFragment,
layoutInflater,
requireView()
)
} else {
setRating(starPosition,false)
if (myRatingsIds.contains(productId)) {
} else {
Log.d(TAG, "starPosition: $starPosition")
productDetailsViewModel.setRatings(
productId, starPosition,
whatStar = starPosition +1,
averageRating = calculateAverageRating(starPosition + 1),
totalRatings = documentSnapshot["total_ratings"] as Long + 1,
myRatingsIds,
myRatings
)
}
}
}
}