简体   繁体   中英

Working with a Firestore Kotlin RecyclerView, I am trying to show a custom object which is a field of the class being displayed in the recycler view

I have a Firestore database holding my data. Here is the structure ( database structure )

subclass database structure

So my booking is sub-collections of the user. Each booking contains its own restaurant as a sub-document.

Here is the activity file:


    private lateinit var binding: ActivityMyBookingsBinding

    //recyclerview for list
    lateinit var recyclerView: RecyclerView

    //Firestore
    private var currentUser: FirebaseUser = FirebaseAuth.getInstance().currentUser!!
    private var db: FirebaseFirestore = FirebaseFirestore.getInstance()

    var query: CollectionReference = db
        .collection("Users")
        .document(currentUser.uid)
        .collection("Bookings")

    //adapter
    lateinit var bookingAdapter: BookingItemAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)


        binding = ActivityMyBookingsBinding.inflate(layoutInflater)
        val view = binding.root
        setupUI()
        setContentView(view)


        setupRecyclerView()
    }


    private fun setupUI() {
        recyclerView = binding.bookingList
    }


    private fun setupRecyclerView(){

        val options: FirestoreRecyclerOptions<BookingItem> = FirestoreRecyclerOptions.Builder<BookingItem>()
            .setQuery(query, BookingItem::class.java)
            .build()

        bookingAdapter = BookingItemAdapter(this, options)

        recyclerView.layoutManager = LinearLayoutManager(this)

        recyclerView.adapter = bookingAdapter

    }

    override fun onStart() {
        super.onStart()
        bookingAdapter.startListening()
    }

    override fun onStop() {
        super.onStop()
        bookingAdapter.stopListening()
    }

Here is the adapter file

class BookingItemAdapter(
    val context: Context,
    val options: FirestoreRecyclerOptions<BookingItem>): FirestoreRecyclerAdapter<BookingItem, BookingItemAdapter.BookingViewHolder>(options) {








    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookingViewHolder {
        return BookingViewHolder(
            LayoutInflater.from(parent.context).inflate(
                R.layout.card_booking,
                parent,
                false,
            ),
        )
    }

    @RequiresApi(Build.VERSION_CODES.N)
    override fun onBindViewHolder(holder: BookingViewHolder, position: Int, model: BookingItem) {
        holder.restaurantNameText.booking_restaurant_name.text = model.getRestaurantItem().getName()
        holder.restaurantDistanceText.booking_distance.text = model.getRestaurantItem().getGeoHash()
        holder.restaurantRatingBar.booking_ratingBar.rating = model.getRestaurantItem().getRating()?.toFloat()!!
        holder.bookingDate.booking_date.text = "${model.getDay()}/${model.getMonth()}/${model.getYear()}"
        holder.bookingTime.booking_time.text = "${model.getHour()}:${model.getMinute()}"
        holder.numberGuests.number_guests.text = model.getGuestNumber()

        val url = model.getRestaurantItem().getRestaurantImage()
        Glide
            .with(holder.restaurantImageItem)
            .load(url)
            .into(holder.restaurantImageItem.booking_restaurantImage)

        holder.itemView.setOnClickListener {
            val intent = Intent(context, RestaurantPageActivity::class.java)
            intent.putExtra("model", model.getRestaurantItem())

            context.startActivity(intent)

        }

        CompletableFuture.runAsync {
            runCatching {
                val url = model.getRestaurantItem().getRestaurantImage()
                Glide
                    .with(holder.restaurantImageItem)
                    .load(url)
                    .into(holder.restaurantImageItem.booking_restaurantImage)

            }
        }
    }

    inner class BookingViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val restaurantNameText: TextView = itemView.findViewById(R.id.booking_restaurant_name)
        val restaurantImageItem: ImageView = itemView.findViewById(R.id.booking_restaurantImage)
        val restaurantRatingBar: RatingBar = itemView.findViewById(R.id.booking_ratingBar)
        val restaurantDistanceText: TextView = itemView.findViewById(R.id.booking_distance)
        val bookingTime: TextView = itemView.findViewById(R.id.booking_time)
        val bookingDate: TextView = itemView.findViewById(R.id.booking_date)
        val numberGuests: TextView = itemView.findViewById(R.id.number_guests)

    }

Here is the bookingItem class:

class BookingItem(restaurantItem: RestaurantItem, guestNumber: String, day: String,
                  month: String, year: String, hour: String, minute: String) {

    constructor(): this(RestaurantItem("", "", 0, 0,
            "", "", "", "", false), "", "", "", "", "","")

    private var restaurantItemName = restaurantItem.getName()
    private var restaurantItemImage = restaurantItem.getRestaurantImage()
    private var restaurantItemPrice = restaurantItem.getPrice()
    private var restaurantItemRating = restaurantItem.getRating()
    private var restaurantItemGeohash = restaurantItem.getGeoHash()
    private var restaurantItemLongitude = restaurantItem.getLongitude()
    private var restaurantItemLatitude = restaurantItem.getLatitude()
    private var restaurantItemCuisine = restaurantItem.getCuisine()
    private var restaurantItemDietary = restaurantItem.getDietaryFriendly()
    private var restaurantItem: RestaurantItem = RestaurantItem(
            restaurantItemName,
            restaurantItemImage,
            restaurantItemPrice,
            restaurantItemRating,
            restaurantItemGeohash,
            restaurantItemLongitude,
            restaurantItemLatitude,
            restaurantItemCuisine,
            restaurantItemDietary)

    private var guestNumber: String = guestNumber
    private var day: String = day
    private var month: String = month
    private var year: String = year
    private var hour: String = hour
    private var minute: String = minute



    fun getRestaurantItem(): RestaurantItem{
        this.restaurantItem.getName()
        this.restaurantItem.getRestaurantImage()
        this.restaurantItem.getPrice()
        this.restaurantItem.getRating()
        this.restaurantItem.getGeoHash()
        this.restaurantItem.getLongitude()
        this.restaurantItem.getLatitude()
        this.restaurantItem.getCuisine()
        this.restaurantItem.getDietaryFriendly()
        return this.restaurantItem
    }
    fun getGuestNumber(): String {
        return this.guestNumber
    }

    fun getDay(): String{
        return this.day
    }

    fun getMonth(): String{
        return this.month
    }

    fun getYear(): String{
        return this.year
    }

    fun getHour(): String{
        return this.hour
    }

    fun getMinute(): String{
        return this.minute
    }
}

Finally incase you need it, here is the sub entity - restaurantItem class:

class RestaurantItem(name: String, restaurantImage: String, price: Long, rating: Long, geoHash: String ,
                     longitude: String, latitude: String, cuisine: String, dietaryFriendly: Boolean): Parcelable{

    constructor(): this ("", "", 0, 0,
        "", "", "", "", false
    )


    private var name: String = name
    private var restaurantImage: String = restaurantImage
    private var price: Long = price
    private var rating: Long = rating
    private var geoHash: String = geoHash
    private var longitude: String = longitude
    private var latitude: String = latitude
    private var cuisine: String = cuisine
    private var dietaryFriendly: Boolean = dietaryFriendly

    private constructor(parcel: Parcel) : this() {
        name = parcel.readString().toString()
        restaurantImage = parcel.readString().toString()
        price = parcel.readLong()
        rating = parcel.readLong()
        geoHash = parcel.readString().toString()
        longitude = parcel.readString().toString()
        latitude = parcel.readString().toString()
        cuisine = parcel.readString().toString()
        dietaryFriendly = parcel.readByte() != 0.toByte()
    }


    fun getName(): String {
        return this.name
    }
    fun getLatitude(): String {
        return this.latitude
    }
    fun getLongitude(): String {
        return this.longitude
    }
    fun getGeoHash(): String {
        return this.geoHash
    }
    fun getPrice(): Long {
        return this.price
    }
    fun getDietaryFriendly(): Boolean{
        return this.dietaryFriendly
    }
    fun getRating(): Long {
        return this.rating
    }
    fun getRestaurantImage(): String {
        return this.restaurantImage
    }
    fun getCuisine(): String {
        return this.cuisine
    }
    

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(name)
        parcel.writeString(restaurantImage)
        parcel.writeLong(price)
        parcel.writeLong(rating)
        parcel.writeString(geoHash)
        parcel.writeString(longitude)
        parcel.writeString(latitude)
        parcel.writeString(cuisine)
        parcel.writeByte(if (dietaryFriendly) 1 else 0)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<RestaurantItem> {
        override fun createFromParcel(parcel: Parcel): RestaurantItem {
            return RestaurantItem(parcel)
        }

        override fun newArray(size: Int): Array<RestaurantItem?> {
            return arrayOfNulls(size)
        }
    }

Now the data is coming in from firebase fine and the time, date, and a number of guests are being displayed correctly.

The restaurant class works because it is used elsewhere and displays correctly in other recycler views.

The issue seems to be that when the booking item is instanciated, all the data in the restaurantItem gets set to its null values.

I walked through with the debugger and I can see the data has made it from firebase.

This is the data when I debug in the bookingitem constructor and step through

This is where the data is lost and reset to null values:

Here is the line that the data is lost

You can access the code here https://github.com/KotaCanchela/RestaurantBookingSystem

I appreciate any help that can be provided, I've tried to give as much detail here as possible.

When you are debugging your code, all the properties of the "restaurantItem" object in the debugger are holding the default values because there is no match between the object of the class and the object in Firestore.

Your "BookingItem" class holds the property called "restaurantItem", while in the database the object is called only "restaurant" , which is not correct. Both names must match. You either change the name of the property in the database to be "restaurantItem" and not just "restaurant", or you should use the following annotations:

@get:PropertyName("restaurant")
@set:PropertyName("restaurant")
@PropertyName("restaurant")

In front of your restaurantItem property in your "BookingItem" class.

Edit:

According to your comment:

do you mean in the BookingItem constructor?

Yes. The minimum implementation for your class might be:

data class BookingItem(
        @get:PropertyName("restaurant")
        @set:PropertyName("restaurant")
        @PropertyName("restaurant")
        var restaurantItem: RestaurantItem? = null,
        var guestNumber: String? = null,
        var day: String? = null,
        var month: String? = null,
        var year: String? = null,
        var hour: String? = null,
        var minute: String? = null
)

Where I have initialized those objects with a null value. Once you get the data from the database, you'll assign the values to your actual fields.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM