Search code examples
kotlinfragmentandroid-spinner

Update a field in the firestore with a selected value of Spinner on hitting a button


My app crashes when I hit the button to update a field in the firestore with spinner value.

The collection name is orders

The document name is dynamic, model.id is used to get it.

The field name is order_status

New value that needs to be updated for the field order_status is to be taken from the spinner.

The code works fine and the field get updated when I hardcode the value, but it crashes when I want to use the selected value of spinner

Following is the error in Logcat

 kotlin.UninitializedPropertyAccessException: lateinit property binding has not been initialized
        at com.trad.ui.adapters.OrderStatusListAdapter.access$getBinding$p(OrderStatusListAdapter.kt:32)
        at com.trad.ui.adapters.OrderStatusListAdapter$onBindViewHolder$1.onClick(OrderStatusListAdapter.kt:84)
        at android.view.View.performClick(View.java:8160)
        at android.widget.TextView.performClick(TextView.java:16222)
        at android.view.View.performClickInternal(View.java:8137)
        at android.view.View.access$3700(View.java:888)
        at android.view.View$PerformClick.run(View.java:30236)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:246)
        at android.app.ActivityThread.main(ActivityThread.java:8512)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)

OrdersByStatusFragment.kt

class OrdersByStatusFragment : BaseFragment() {

    private lateinit var binding: OrderStatusLayoutBinding

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
       
        return inflater.inflate(R.layout.fragment_orders_by_status, container, false)
    }

    override fun onResume() {
        super.onResume()

        getOrderStatusList()
    }

    private fun getOrderStatusList() {
       
        showProgressDialog(resources.getString(R.string.please_wait))

        FirestoreClass().getOrderStatusList(this@OrdersByStatusFragment)
    }

    fun successOrderStatusList(orderStatusList: ArrayList<OrderStatus>) {

        hideProgressDialog()

        if (orderStatusList.size > 0) {

            rv_order_by_status.visibility = View.VISIBLE
            tv_no_orders_by_status_found.visibility = View.GONE

            rv_order_by_status.layoutManager = LinearLayoutManager(activity)
            rv_order_by_status.setHasFixedSize(true)

            val orderStatusListAdapter =
                OrderStatusListAdapter(requireActivity(), orderStatusList)

            rv_order_by_status.adapter = orderStatusListAdapter

        } else {

            rv_order_by_status.visibility = View.GONE
            tv_no_orders_by_status_found.visibility = View.VISIBLE
        }
    }

    fun successNewOrderStatus() {

        Toast.makeText(requireContext(), "Success", Toast.LENGTH_SHORT).show()

    }
}

OrderStatusListAdapter.kt

open class OrderStatusListAdapter(
    private val context: Context,
    private var list: ArrayList<OrderStatus>,

    ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
  
    private lateinit var binding: OrderStatusLayoutBinding

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {

        return MyViewHolder(OrderStatusLayoutBinding.inflate(LayoutInflater.from(parent.context), parent,false))
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val model = list[position]

        if (holder is MyViewHolder) {

            GlideLoader(context).loadProductPicture(
                model.image,
                holder.itemView.iv_order_status_item_image
            )

            val dateFormat = "dd MMM yyyy HH:mm"
           
            val formatter = SimpleDateFormat(dateFormat, Locale.getDefault())

            val calendar: Calendar = Calendar.getInstance()
            calendar.timeInMillis = model.order_datetime

            val orderDateTime = formatter.format(calendar.time)
            holder.itemView.tv_order_status_order_date.text = orderDateTime

            holder.itemView.tv_order_status_item_name.text = model.items[0].title
            holder.itemView.tv_order_status_item_price.text = "$${model.total_amount}"

            holder.itemView.tv_order_status.text = model.order_status
            holder.itemView.tv_order_status_order_id.text = model.id

            holder.itemView.btn_order_status_change_status.setOnClickListener {

                binding.spnOrderChangeStatus.onItemSelectedListener =
                    object : AdapterView.OnItemSelectedListener {
                        override fun onItemSelected(
                            parent: AdapterView<*>?,
                            view: View?,
                            position: Int,
                            id: Long
                        ) {
                            Toast.makeText(
                                context,
                                "Selected value is ${
                                    parent?.getItemAtPosition(position).toString()
                                }",
                                Toast.LENGTH_SHORT
                            ).show()

                            val newstat = parent?.getItemAtPosition(position).toString()
                            FirestoreClass().updateOrderStatus(model.id, newstat)

                        }

                        override fun onNothingSelected(parent: AdapterView<*>?) {

                        }
                    }

           }

            holder.itemView.ib_order_status_delete_product.visibility = View.GONE

            holder.itemView.setOnClickListener {
                val intent = Intent(context, SoldProductDetailsActivity::class.java)
                intent.putExtra(Constants.EXTRA_SOLD_PRODUCT_DETAILS, model)
                context.startActivity(intent)
            }


        }

    }

    override fun getItemCount(): Int {
        return list.size
    }

    inner class MyViewHolder(private val binding : OrderStatusLayoutBinding) : RecyclerView.ViewHolder(binding.root)
}

FirestoreClass.kt

fun updateOrderStatus(
    orderID: String,
    newOrderStatus: String
) {
    mFireStore.collection(Constants.ORDERS)
        .document(orderID)
        .update("order_status", newOrderStatus)
        .addOnSuccessListener {


        }
        .addOnFailureListener {
        }
}

EDIT


Following is the new error.

2021-06-09 01:24:17.550 2253-2253/com.tradaxis E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.tradaxis, PID: 2253
    java.lang.NullPointerException: rv_order_by_status must not be null
        at com.tradaxis.ui.fragments.OrdersByStatusFragment.successOrderStatusList(OrdersByStatusFragment.kt:76)
        at com.tradaxis.firestore.FirestoreClass$getOrderStatusList$1.onSuccess(FirestoreClass.kt:935)
        at com.tradaxis.firestore.FirestoreClass$getOrderStatusList$1.onSuccess(FirestoreClass.kt:22)
        at com.google.android.gms.tasks.zzn.run(Unknown Source:4)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:246)
        at android.app.ActivityThread.main(ActivityThread.java:8512)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)

rv_order_by_status.visibility = View.VISIBLE in 'fun successOrderStatusList(orderStatusList: ArrayList)' is the code at OrdersByStatusFragment.kt:76

fragment.successOrderStatusList(list) in FirestoreClass.kt is the code at FirestoreClass.kt:935

class FirestoreClass { is the code at FirestoreClass.kt:22

NOTE: There was no such error for these lines of code before making these changes.


Solution

  • In your OrderStatusFragment OnCreateView you aren't setting the binding. You need to do something like this

     private var _binding: ResultProfileBinding? = null
    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = ResultProfileBinding.inflate(inflater, container, false)
        val view = binding.root
        return view
    }
    
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
    

    Instead of ResultProfileBinding you'd use your OrderStatusLayoutFragment. This is unrelated to firebase, but it should fix the crash you are currently having.

    Read more: https://developer.android.com/topic/libraries/view-binding

    Don't forget the ondestroy, to avoid memory leaks.