Xpense Dev Retrospective
Every screen in Xpense is a plain XML file in res/layout/. XML doesn't draw anything by itself. It describes a tree of views, and at runtime Android's LayoutInflater walks that tree and creates the real, on-screen objects it describes. What's on the screen is the inflated result, not the XML file itself.
item_expense.xml is the layout for a single row in the expense history list. It's short enough to show in full:
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:background="@color/card" android:padding="16dp"> <LinearLayout android:orientation="horizontal"> <TextView android:id="@+id/tvCategory" android:layout_weight="1" android:textColor="@color/accent_blue"/> <TextView android:id="@+id/tvDate"/> </LinearLayout> <LinearLayout android:orientation="horizontal"> <TextView android:id="@+id/tvDescription" android:layout_weight="1"/> <TextView android:id="@+id/tvAmount" android:textColor="@color/danger"/> </LinearLayout> </LinearLayout>
Two rows of two TextViews each. Category and date on top, description and amount below. That's the entire visual structure behind every line in the expense history.
Why layout_weight="1" on the category and description: in a horizontal LinearLayout, weight tells a view to expand and take up whatever space is left over after everything else is measured. Category expands to push the date to the far right. Description expands to push the amount to the far right. Neither the category text nor the description text has a fixed width, they just take up whatever room remains.
Notice nothing in that file says an actual hex value. @color/card, @color/accent_blue, @color/danger, all names. Android resolves each name to an actual color at runtime, and which actual color it resolves to depends on whether values/colors.xml or values-night/colors.xml is active. Same layout file, two completely different results, and the layout itself never has to know which one it's getting.
ConstraintLayout is in the project's dependencies, but almost every real screen in Xpense, like activity_add_expense.xml, is built with a plain ScrollView wrapping a vertical LinearLayout:
<ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/background"> <LinearLayout android:orientation="vertical" android:padding="24dp"> <TextView android:text="Add Expense"/> <EditText android:id="@+id/etAmount" android:inputType="numberDecimal" android:hint="0"/> <!-- more fields stack below, same pattern --> </LinearLayout> </ScrollView>
Why not use ConstraintLayout everywhere: ConstraintLayout earns its complexity on screens with overlapping or non-linear positioning, views anchored to each other in a web of constraints. Every input screen in Xpense is genuinely just a vertical stack of fields, one after another. A LinearLayout inside a ScrollView does exactly that with less to get wrong, so that's what got used. The dependency exists mostly because Android Studio templates pull it in by default, not because the app leans on it.
android:id="@+id/tvAmount" doesn't do anything visually. It's a name. Android generates a numeric constant for it at compile time, and that's what lets Kotlin code reach into the inflated layout and grab the exact TextView this ID points to. That connection, XML declares the view and gives it a name, Kotlin looks it up by that name, is the entire subject of the next post.
XML describes what a screen looks like. Next up, how the actual Kotlin code reaches into that inflated layout and makes it interactive.