Xpense Dev Retrospective
Xpense doesn't use View Binding or synthetic imports. Every single view is looked up the plain way, right after setContentView, and stored in a field for the rest of the Activity's life:
setContentView(R.layout.activity_add_expense) etAmount = findViewById(R.id.etAmount) etDescription = findViewById(R.id.etDescription) chipGroupCategories = findViewById(R.id.chipGroupCategories) btnSave = findViewById(R.id.btnSave)
Why not View Binding: mostly that the app started this way and switching midway means touching every Activity for no functional gain. findViewById is more verbose, but it's also completely explicit, there's no generated binding class doing something behind the scenes. Every lookup is a line you can see.
R.id.etAmount is a number Android generates from the layout XML's android:id="@+id/etAmount". That number is the entire connection. If the ID in the XML gets renamed and the Kotlin reference doesn't, it fails at compile time, not silently, since R.id.etAmount simply stops existing.
btnBack.setOnClickListener { goBack() } btnNext.setOnClickListener { goNext() }
The lambda passed to setOnClickListener is just a block of code Android runs the instant that view is tapped. Nothing more mysterious than that.
The cleanest real example of an Activity receiving data through an Intent is how AddExpenseActivity detects edit mode:
val currentIntent = intent if (currentIntent.hasExtra("edit_id")) { isEditMode = true editId = currentIntent.getStringExtra("edit_id") val amount = currentIntent.getDoubleExtra("edit_amount", 0.0) val timestamp = currentIntent.getLongExtra("edit_timestamp", 0) editCurrency = currentIntent.getStringExtra("edit_currency") editResolved = currentIntent.getBooleanExtra("edit_resolved", false) etAmount.setText(amount.formatAmount()) // ...prefill every other field from the extras above }
Same Activity, same layout, used for both adding a brand new expense and editing an existing one. The only difference is whether the Intent that launched it happened to carry an edit_id extra. No extra, it behaves as add mode. Extra present, it prefills every field and switches into edit mode.
Every value pulled out here has to match, exactly, what was put in. getDoubleExtra reading a key that was put in as a String doesn't error loudly, it just silently returns the fallback value. editCurrency and editResolved specifically exist because an earlier version of this screen didn't thread those two extras through at all, editing an expense silently reset its currency to whatever the current default was, and silently reset its resolved status back to false. Neither failed loudly. Both just quietly produced wrong data, which is the more dangerous kind of bug to have in code that touches money.
This is the entire mechanism connecting XML, IDs, and Kotlin. Next up, moving away from the UI layer and into where the actual data lives, starting with how Xpense stored everything before Room ever entered the picture.