Material Components
Material Components for Android (the com.google.android.material
library) is Google's own official implementation of the Material Design system — the buttons, text
fields, cards, and navigation widgets that give a modern Android app its familiar, polished feel. This
lesson tours the components TrailLog leans on most, and builds its "add trail" form as a full worked
example.
Commonly Used Material Components
| Component | What It's For |
|---|---|
MaterialButton | A Material-styled button — filled, outlined, or text style — replacing the plain Button widget. |
TextInputLayout + TextInputEditText | A text field with a floating label that animates above the input as the user types, plus built-in support for showing an error message beneath the field. |
FloatingActionButton | A circular, elevated button reserved for a screen's single most important action — on TrailLog's list screen, this is the "add new trail" button. |
MaterialCardView | A Material-styled card with proper elevation, corner radius, and stroke support — used for each row in TrailLog's trail list. |
BottomNavigationView | A row of destinations pinned to the bottom of the screen, for switching between a small number of top-level sections (for example, Trails, Map, and Profile). |
The Required Base Theme
Material Components have a real, common beginner gotcha: they need the app's theme (in
res/values/themes.xml) to descend from a Material theme, or they render broken and
unstyled — flat, colorless, missing the elevation and ripple effects that define Material Design.
<!-- res/values/themes.xml -->
<style name="Theme.TrailLog" parent="Theme.Material3.DayNight.NoActionBar">
<item name="colorPrimary">@color/trail_green</item>
</style>
Theme.Material3.DayNight is Google's current Material 3 base theme, and the
DayNight part means it automatically switches between a light and dark palette based on
the system setting. If a MaterialButton or TextInputLayout in TrailLog ever
looks plain, square, or oddly colored, the very first thing to check is whether the app's theme
actually extends a Theme.Material3 (or the older Theme.MaterialComponents)
parent rather than a plain Theme.AppCompat one.
Worked Example: TrailLog's Add-Trail Form
Here's the name field from TrailLog's "add trail" screen, using TextInputLayout wrapped
around a TextInputEditText:
<!-- res/layout/fragment_add_trail.xml (excerpt) -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/nameInputLayout"
android:hint="Trail name"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/nameEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
android:hint on the TextInputLayout (not the inner
EditText) is what becomes the floating label. When the user taps in and the field is
empty, "Trail name" sits inside the box as a normal hint; once they start typing, it animates up above
the input, staying visible even while there's text in the field.
Validating the field and showing an error uses a single property on the TextInputLayout:
private fun validateName(): Boolean {
val name = binding.nameEditText.text?.toString()?.trim().orEmpty()
return if (name.isEmpty()) {
binding.nameInputLayout.error = "Name is required"
false
} else {
binding.nameInputLayout.error = null // clear any previous error
true
}
}
Setting textInputLayout.error draws the error text in red beneath the field and outlines
the box in the error color — all built in, with no manual view for the error message needed. Setting
it back to null once the field becomes valid clears that state.
Worked Example: The FloatingActionButton
TrailLog's trail list screen anchors a FloatingActionButton to its bottom-right corner as
the one obvious way to log a new hike:
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/addTrailFab"
android:contentDescription="Add trail"
app:srcCompat="@drawable/ic_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
Wiring it up in the Fragment launches the add-trail screen:
binding.addTrailFab.setOnClickListener {
findNavController().navigate(R.id.action_trailList_to_addTrail)
}