Search code examples
androidkotlinandroid-activityserializable

Initializing null object and pass between activities before saving


I am trying to do something which is very complicated to me.... I am trying to create an invoice with two objects 1) Invoice 2) InvoiceItems (Line Items)

The same activity is used to edit existing or to create a new invoice hence both Invoice and InvoiceItems are nullable.

The way I am trying to make this work is when the activity is launched,

  1. user enter details in the form for the invoice activity
  2. user clicks add Item details (Invoice with data entered like title etc, and invoiceItems are sent to the edit InvoiceItems activity, on Click)
  3. New activity is launched to add a single Item detail
  4. Changes made and the invoice items is updated
  5. On press back, the objects are sent back to the previous activity again

The issues are two fold:

  1. Is this the right approach?
  2. I get null point error exception when adding the field details to the invoice object before sending to the InvoiceItem activity

Please have a look at the code below:

class EditInvoice : AppCompatActivity() {

companion object {
    @JvmStatic
    fun start(context: Context, invoice: Invoice?, invoiceItems: InvoiceItems?) {
        val starter = Intent(context, EditInvoice::class.java)
            .putExtra("invoice", invoice)
            .putExtra("invoiceItems", invoiceItems)
        context.startActivity(starter)
    }
}

private var invoice: Invoice? = null
private var invoiceEdit: Invoice? = null
private lateinit var contact: Contact
private var invoiceItems: List<InvoiceItems>? = null
private lateinit var dueDate: Calendar
private val calendar = Calendar.getInstance()
private var total = 0
private var subTotal = 0
private var taxrate = 0
private var invoiceType: String = ""
private var invoiceUpdt: InvoiceItems? = null
private var j: Int = 0
private var clientLkey: String? = ""

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_edit_invoice)

    val toolbar: Toolbar = findViewById(R.id.toolbar_editinv)
    setSupportActionBar(toolbar)
    supportActionBar?.setDisplayShowHomeEnabled(true)

    val invoiceClient = findViewById<AutoCompleteTextView>(R.id.invoiceClient)
    val invoiceDueDt = findViewById<TextView>(R.id.invoiceDueDt)
    dueDate = Calendar.getInstance()

    //getting values from intent
    invoice = intent.getSerializableExtra("invoice") as? Invoice
    invoiceItems = intent.getSerializableExtra("invoiceItem") as? List<InvoiceItems>
    invoiceUpdt = intent.getSerializableExtra("invoiceItemUpdt") as? InvoiceItems
    j = intent.getIntExtra("i",0)

    if (invoice == null){
        invoiceType = "new"
        edit_inv.text = "Add Invoice"
        invoiceEdit = Invoice("0","0","0","0","0","0",0,0,null,"")
        addinvoiceItem()
    } else {
        invoiceEdit = invoice
        editInvoice()
    }

    //Setup Due date for the invoice
    invoiceDueDt.setOnClickListener {
        showCalendar()
    }

    //Auto complete based on database for selecting the client
    val clientContact: List<Contact> = ArrayList<Contact>()
    val adapter = ClientSelectAdapter(this, R.layout.userlatomcontacts, clientContact)
    invoiceClient.setAdapter(adapter)
    invoiceClient.threshold = 2

    invoiceClient.setOnItemClickListener { parent, _, position, id ->
        val selectedClient = parent.adapter.getItem(position) as Contact?
        invoiceClient.setText(selectedClient?.name)
        clientLkey = selectedClient?.lookupKey
    }

    val saveInvoice = findViewById<TextView>(R.id.editinv_save)
    val invoiceTitle = findViewById<EditText>(R.id.invoiceTitle)

    saveInvoice.setOnClickListener {

        if(invoiceTitle.toString().isEmpty()){
            Toast.makeText(this, "Invoice title can't be empty", Toast.LENGTH_SHORT).show()
            return@setOnClickListener
        }
        if(invoiceClient.toString().isEmpty()){
            Toast.makeText(this, "Please select a Client for the invoice", Toast.LENGTH_SHORT).show()
            return@setOnClickListener
        }
        if(invoiceDueDt.toString().isEmpty()){
            Toast.makeText(
                this,
                "Please enter the due date for the invoice",
                Toast.LENGTH_SHORT
            ).show()
            return@setOnClickListener
        }
        if(invoiceItems == null){
            Toast.makeText(
                this,
                "Please enter the line items/services for the invoice",
                Toast.LENGTH_SHORT
            ).show()
            return@setOnClickListener
        }

        //updating values as current
        updateInvoiceValues()

        //Storing values to DB
        val db = AppDatabase.getDatabase(this)
        if(invoiceType == "new"){
            db.InvoicesDao().addInvoice(invoice!!)
            for(i in invoiceItems!!.indices){
                db.InvoiceItemsDao().addInvItem(invoiceItems!![i])
            }
        } else {
            db.InvoicesDao().updateInvoice(invoice!!)
            for(i in invoiceItems!!.indices){
                db.InvoiceItemsDao().updateInvItem(invoiceItems!![i])
            }
        }
    }

}

inner class ClientSelectAdapter(
    context: Context,
    @LayoutRes private val layoutResource: Int,
    private var allContacts: List<Contact>
):
    ArrayAdapter<Contact>(context, layoutResource, allContacts),
    Filterable {private var mContact: List<Contact> = allContacts

    override fun getCount(): Int {
        return mContact.size
    }

    override fun getItem(p0: Int): Contact {
        return mContact[p0]

    }
    override fun getItemId(p0: Int): Long {
        // Or just return p0
        return mContact[p0].id.toLong()
    }

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {

        var view = convertView
        if (view == null) {
            view = LayoutInflater.from(parent.context)
                .inflate(layoutResource, parent, false)
        }
        val invoiceClient = view!!.findViewById<View>(R.id.invClientName) as TextView
        invoiceClient.text = mContact[position].name
        val clientProfile = view.findViewById<View>(R.id.profile_image) as ShapeableImageView

        Picasso.get().load(mContact[position].photoUri)
            .placeholder(R.drawable.ic_baseline_whatshot_24).fit().centerCrop()
            .into(clientProfile)

        val contLabel = view.findViewById<View>(R.id.salesLabelText) as TextView

        if(mContact[position].label != null){
            contLabel.text = mContact[position].label
            if (mContact[position].label == "Lead" || mContact[position].label == "LEAD"){
                contLabel.setBackgroundColor(resources.getColor(R.color.ColorPurple))
            } else if (mContact[position].label == "Qualified"){
                contLabel.setBackgroundColor(resources.getColor(R.color.ColorBlueNote))
            } else if (mContact[position].label== "Proposal"){
                contLabel.setBackgroundColor(resources.getColor(R.color.ColorMaroon))
            } else if (mContact[position].label == "Client"){
                contLabel.setBackgroundColor(resources.getColor(R.color.ColorGreen))
            } else if (mContact[position].label == "Invoiced"){
                contLabel.setBackgroundColor(resources.getColor(R.color.ColorYellow))
            } else if (mContact[position].label == "Unpaid"){
                contLabel.setBackgroundColor(resources.getColor(R.color.ColorRed))
            } else if (mContact[position].label == ""){
                salesLabel.visibility = View.GONE
            }
        }

        return view
    }

    override fun getFilter(): Filter {
        return object : Filter() {
            override fun publishResults(
                charSequence: CharSequence?,
                filterResults: FilterResults
            ) {
                mContact = filterResults.values as List<Contact>
                notifyDataSetChanged()
            }

            override fun performFiltering(charSequence: CharSequence?): FilterResults {
                var queryString = charSequence?.toString()?.toLowerCase(Locale.ROOT)

                val results = FilterResults()
                results.values = if (queryString == null || queryString.isEmpty())
                    allContacts
                else {
                    queryString = "%${charSequence}%"
                    val db = AppDatabase.getDatabase(context)
                    allContacts = db.contactsDao().getBySearch(queryString)
                    allContacts
                }
                return results
            }


        }
    }
}

private fun showCalendar() {

    val datePicker = DatePickerDialog(
        this,
        R.style.DateTimePickerTheme,
        { datePicker: DatePicker, year: Int, month: Int, day: Int ->
            dueDate.set(year, month, day)
            updateDateFields()
        },
        calendar.get(Calendar.YEAR),
        calendar.get(Calendar.MONTH),
        calendar.get(Calendar.DAY_OF_MONTH)
    )
    datePicker.show()
}

private fun updateDateFields() {
    val formatter = SimpleDateFormat("MMM dd, yyyy", Locale.getDefault())
    val invoiceDueDt = findViewById<TextView>(R.id.invoiceDueDt)
    if(dueDate.time < Calendar.getInstance().time)
    {
        Toast.makeText(this, "Please select a date that is today or later", Toast.LENGTH_SHORT).show()
    } else {
        invoiceDueDt.text = formatter.format(dueDate.time)
    }
}


private fun editInvoice() {

    val invoiceTitle = findViewById<EditText>(R.id.invoiceTitle)
    val invoiceClient = findViewById<AutoCompleteTextView>(R.id.invoiceClient)
    val itemOtherDetails = findViewById<EditText>(R.id.itemOtherDetails)

    if(invoice!!.invoiceTitle != "0"){
        invoiceTitle.setText(invoice!!.invoiceTitle)
    }
    if(invoice!!.invoiceClientLKey != "0"){
        //getting client name from the database
        val getClient = AppDatabase.getDatabase(this)
        contact = getClient.contactsDao().getSingleContact(invoice!!.invoiceClientLKey!!)
        invoiceClient.setText(contact.name)
    }
    if(invoice!!.othComments != "0"){
        itemOtherDetails.setText(invoice!!.othComments)
    }

    addinvoiceItem()

    invoiceClient.setOnClickListener{
        val intent = Intent(this, ContactDetailsHome::class.java)
        intent.putExtra("contact", contact as Serializable)
        this.startActivity(intent)
    }
}

private fun addinvoiceItem() {

    val invoiceItemsLayout = findViewById<RelativeLayout>(R.id.invoiceItemsLayout)
    val invoiceSubValue = findViewById<TextView>(R.id.invoiceSubValue)
    val invoiceTaxValue = findViewById<TextView>(R.id.invoiceTaxValue)
    val invoiceTotalValue = findViewById<TextView>(R.id.invoiceTotalValue)

    if(invoice != null) {

        if(intent.getSerializableExtra("invoiceItem") == null) {
            //getting invoice items stored in the local db
            val getItems = AppDatabase.getDatabase(this)
            invoiceItems = getItems.InvoiceItemsDao().getinvItem(invoice!!.invNo)
        }

        for (i in invoiceItems!!.indices+1) {

            //Check if invoiceItems have been received from EditBillingItems if yes, then update the particular object
            if(j == i){
                invoiceItems!![i].itemTitle = invoiceUpdt!!.itemTitle
                invoiceItems!![i].itemDesc = invoiceUpdt!!.itemDesc
                invoiceItems!![i].itemQty = invoiceUpdt!!.itemQty
                invoiceItems!![i].itemRate = invoiceUpdt!!.itemRate
                invoiceItems!![i].itemTaxable = invoiceUpdt!!.itemTaxable
                invoiceItems!![i].itemTotal = invoiceUpdt!!.itemTotal
            }


            //Item Name Display on Invoice Activity
            val itemTitle: ArrayList<TextView>? = null
            itemTitle!![i] = TextView(this)
            val layoutParams: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams(
                RelativeLayout.LayoutParams.WRAP_CONTENT,
                RelativeLayout.LayoutParams.WRAP_CONTENT
            ) // or wrap_content
            layoutParams.setMargins(0, 0, 0, 0)
            layoutParams.addRule(RelativeLayout.ALIGN_PARENT_START)
            itemTitle[i].hint = "Enter Item Title"
            itemTitle[i].textSize = 16f
            itemTitle[i].layoutParams = layoutParams
            itemTitle[i].setTextColor(resources.getColor(R.color.txtcolor))
            invoiceItemsLayout.addView(itemTitle[i])

            itemTitle[i].text = invoiceItems!![i].itemTitle


            //Total Item Value
            val itemValue: ArrayList<TextView>? = null
            itemValue!![i] = TextView(this)
            val layoutParams2: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams(
                RelativeLayout.LayoutParams.WRAP_CONTENT,
                RelativeLayout.LayoutParams.WRAP_CONTENT
            ) // or wrap_content
            layoutParams2.setMargins(0, 0, 0, 0)
            layoutParams2.addRule(RelativeLayout.ALIGN_PARENT_END)
            itemValue[i].text = "00.00"
            itemValue[i].textSize = 16f
            itemValue[i].setTextColor(resources.getColor(R.color.txtcolor))
            itemValue[i].setPadding(10, 0, 0, 10)
            itemValue[i].layoutParams = layoutParams2
            invoiceItemsLayout.addView(itemValue[i], layoutParams)


            itemValue[i].text = invoiceItems!![i].itemTotal.toString()

            if(invoiceItems!![i + 1].itemTitle == ""){
                //New Item Title field
                val itemTitle: ArrayList<TextView>? = null
                itemTitle!![i] = TextView(this)
                val layoutParams: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams(
                    RelativeLayout.LayoutParams.WRAP_CONTENT,
                    RelativeLayout.LayoutParams.WRAP_CONTENT
                ) // or wrap_content
                layoutParams.setMargins(0, 0, 0, 0)
                layoutParams.addRule(RelativeLayout.ALIGN_PARENT_START)
                itemTitle[i].hint = "Enter Item Title"
                itemTitle[i].textSize = 16f
                itemTitle[i].setTextColor(resources.getColor(R.color.txtcolor))
                invoiceItemsLayout.addView(itemTitle[i], layoutParams)

                //New item value field
                val itemValue: ArrayList<TextView>? = null
                itemValue!![i] = TextView(this)
                val layoutParams2: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams(
                    RelativeLayout.LayoutParams.WRAP_CONTENT,
                    RelativeLayout.LayoutParams.WRAP_CONTENT
                ) // or wrap_content
                layoutParams2.setMargins(0, 0, 0, 0)
                layoutParams2.addRule(RelativeLayout.ALIGN_PARENT_END)
                itemValue[i].text = "00.00"
                itemValue[i].textSize = 16f
                itemValue[i].setTextColor(resources.getColor(R.color.txtcolor))
                itemValue[i].setPadding(10, 0, 0, 10)
                itemValue[i].layoutParams = layoutParams2
                invoiceItemsLayout.addView(itemValue[i], layoutParams)

                itemTitle[i].setOnClickListener {

                    val invoiceItemSend: InvoiceItems = invoiceItems!![i]

                    updateInvoiceValues()

                    val intent = Intent(this, EditBillingItem::class.java)
                    intent.putExtra("invoiceItem", invoiceItems as Serializable)
                    intent.putExtra("index", i)
                    intent.putExtra("invoice", invoice as Serializable)
                    this.startActivity(intent)
                }
            }
            subTotal += invoiceItems!![i].itemTotal
            val taxValue = invoiceItems!![i].itemRate!!
            taxrate += (subTotal * taxValue)
        }
        invoiceSubValue.setText(subTotal)
        total = subTotal + taxrate
        invoiceTaxValue.setText(taxrate)
        invoiceTotalValue.setText(total)

    } else {

        //Item Name Display on Invoice Activity
        val layoutParams: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams(
            RelativeLayout.LayoutParams.WRAP_CONTENT,
            RelativeLayout.LayoutParams.WRAP_CONTENT
        ) // or wrap_content
        layoutParams.setMargins(0, 0, 0, 0)
        layoutParams.addRule(RelativeLayout.ALIGN_PARENT_START)
        val itemTitle = TextView(this)
        itemTitle.hint = "Enter Item Title"
        itemTitle.textSize = 16f
        itemTitle.setTextColor(resources.getColor(R.color.txtcolor))
        invoiceItemsLayout.addView(itemTitle, layoutParams)

        //Total Item Value
        val itemValue = TextView(this)
        val layoutParams2: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams(
            RelativeLayout.LayoutParams.WRAP_CONTENT,
            RelativeLayout.LayoutParams.WRAP_CONTENT
        ) // or wrap_content
        layoutParams2.setMargins(0, 0, 0, 0)
        layoutParams2.addRule(RelativeLayout.ALIGN_PARENT_END)
        itemValue.text = "00.00"
        itemValue.textSize = 16f
        itemValue.setTextColor(resources.getColor(R.color.txtcolor))
        itemValue.setPadding(10, 0, 0, 10)
        itemValue.layoutParams = layoutParams2
        invoiceItemsLayout.addView(itemValue, layoutParams2)

        itemTitle.setOnClickListener {
            updateInvoiceValues()
            val intent = Intent(this, EditBillingItem::class.java)
            if(invoiceItems != null){
                intent.putExtra("invoiceItem", invoiceItems as Serializable)
            }
            if(invoice != null){
                intent.putExtra("invoice", invoice as Serializable)
            }
            intent.putExtra("index",0)
            this.startActivity(intent)
        }
    }
}

private fun updateInvoiceValues() {
    //updating values

    val invoiceTitle = findViewById<EditText>(R.id.invoiceTitle)
    val invoiceDueDt = findViewById<TextView>(R.id.invoiceDueDt)

    if(invoiceTitle.text.toString().isNotEmpty()){
        invoiceEdit?.invoiceTitle = invoiceTitle.text.toString()
    }
    if(clientLkey != ""){
        invoiceEdit?.invoiceClientLKey = clientLkey
    }
    if (invoiceDueDt.text.toString().isNotEmpty()){
        invoiceEdit?.dueDate = invoiceDueDt.text.toString()
    }

}

ACTIVTY B - edit Item

class EditBillingItem : AppCompatActivity() {

companion object {
    @JvmStatic
    fun start(context: Context, invoice: Invoice?, invoiceItems: InvoiceItems?) {
        val i = 0
        val starter = Intent(context, EditInvoice::class.java)
            .putExtra("invoice", invoice)
            .putExtra("invoiceItem", invoiceItems)
            .putExtra("i", i)
        context.startActivity(starter)
    }
}

private var invoiceItem: List<InvoiceItems>? = null
private var invoice: Invoice? = null
private lateinit var invoiceItemUpdt: InvoiceItems
private var i = 0

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_billing_item_update)

    invoice = intent.getSerializableExtra("invoiceEdit") as Invoice
    invoiceItem = intent.getSerializableExtra("invoiceItem") as? List<InvoiceItems>
    i = intent.getIntExtra("i", i)

    val itemTitle = findViewById<EditText>(R.id.itemTitle)
    val itemDesc = findViewById<EditText>(R.id.itemDesc)
    val itemQty = findViewById<EditText>(R.id.itemQty)
    val itemChrgRt = findViewById<EditText>(R.id.itemChrgRt)
    val itemTaxable = findViewById<SwitchCompat>(R.id.itemTaxable)
    val itemTotal = findViewById<TextView>(R.id.itemTotal)
    val itemBack = findViewById<ImageButton>(R.id.itemBack)

    if(intent.getSerializableExtra("invoiceItems") != null){

        itemTitle.setText(invoiceItem!![i].itemTitle)
        itemDesc.setText(invoiceItem!![i].itemDesc)
        itemQty.setText(invoiceItem!![i].itemQty.toString())
        itemChrgRt.setText(invoiceItem!![i].itemRate.toString())
        itemTaxable.isChecked = invoiceItem!![i].itemTaxable == true
        itemTotal.setText(invoiceItem!![i].itemTotal)
    }

    itemChrgRt.addTextChangedListener(object : TextWatcher {

            override fun afterTextChanged(s: Editable) {}

            override fun beforeTextChanged(s: CharSequence, start: Int,
                                           count: Int, after: Int) {
            }

            override fun onTextChanged(s: CharSequence, start: Int,
                                       before: Int, count: Int) {

                if(s.toString().isNotEmpty() && itemQty.text.toString().isNotEmpty()){

                    var i = itemQty.text.toString().toInt()
                    val j = s.toString().toInt()
                    i *= j
                    itemTotal.text = i.toString()
                }
            }
        })

    itemQty.addTextChangedListener(object : TextWatcher {

        override fun afterTextChanged(s: Editable) {}

        override fun beforeTextChanged(s: CharSequence, start: Int,
                                       count: Int, after: Int) {
        }

        override fun onTextChanged(s: CharSequence, start: Int,
                                   before: Int, count: Int) {

            if(itemChrgRt.text.toString().isNotEmpty() && itemChrgRt.text.toString().isNotEmpty()){

                var i = s.toString().toInt()
                val j = itemChrgRt.text.toString().toInt()
                i *= j
                itemTotal.text = i.toString()
            }

        }
    })

    itemBack.setOnClickListener {

        if(itemTitle.toString().isBlank() && itemQty.toString().isNotBlank() || itemChrgRt.toString().isNotBlank() && itemTitle.toString().isBlank()){
            Toast.makeText(this, "Title can't be empty", Toast.LENGTH_SHORT).show()
            return@setOnClickListener
        }
        invoiceItemUpdt = InvoiceItems("0","0","0",null,null,null,null,0)

        invoiceItemUpdt.itemTitle = itemTitle.toString()
        invoiceItemUpdt.itemTitle = itemDesc.toString()
        if(itemQty.toString().isEmpty()){
            invoiceItemUpdt.itemQty = 1
        } else {
            invoiceItemUpdt.itemQty = itemQty.text.toString().toInt()
        }
        invoiceItemUpdt.itemRate = itemChrgRt.text.toString().toInt()
        itemTaxable.isChecked = invoiceItemUpdt.itemTaxable == true

        val intent = Intent(this, EditInvoice::class.java)
        intent.putExtra("invoice", invoice as Serializable)
        intent.putExtra("invoiceItem", invoiceItem as Serializable)
        intent.putExtra("invoiceItemUpdt", invoiceItemUpdt as Serializable)
        intent.putExtra("index", i)
        this.startActivity(intent)
    }
}

enter image description here enter image description here

Error from logcat java.lang.NullPointerException: null cannot be cast to non-null type java.io.Serializable at in.latom.latom.Billing.ui.EditInvoice$addinvoiceItem$2.onClick(EditInvoice.kt:449) at android.view.View.performClick(View.java:7398) at android.view.View.performClickInternal(View.java:7375) at android.view.View.access$3700(View.java:817) at android.view.View$PerformClick.run(View.java:28516) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7858) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:984)


Solution

  • Your invoice variable will be null if this line returns null: invoice = intent.getSerializableExtra("invoice") as? Invoice

    Later in your code, you had never populated your invoice variable, but you force unwraps it in this line:

    db.InvoicesDao().addInvoice(invoice!!)
    

    This if statement always be null:

    if(invoice != null){
       intent.putExtra("invoice", invoice as Serializable)
    }