How to Build a CRUD App With Kotlin and Android Studio

Chapter 8 Layout, Themes, Navigation and activity_main.xml

Problems covered in this chapter
  • Dark mode

  • Defining attributes in our themes

Next, we want to integrate the menus into our app by including them in the layout file activity_main.xml. Before we do this, however, we have to expand our two string.xml files and add a few entries to the two themes.xml files (for light and dark mode). We also need to add the new vector icon ’Save’ (baseline_save_24.xml), which is used for the floating action button to export to a CSV file.

8.1 themes.xml (Light-Mode)

Listing 22: themes.xml (Light-Mode)
<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.CrudDiary" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/purple_500</item>
        <item name="colorPrimaryVariant">@color/purple_700</item>
        <item name="colorOnPrimary">@color/white</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">@color/teal_200</item>
        <item name="colorSecondaryVariant">@color/teal_700</item>
        <item name="colorOnSecondary">@color/black</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
        <item name="active_background_color">@color/white</item>
        <item name="inactive_background_color">@color/colorLightGrey</item>
        <item name="main_user_color">@color/teal_700</item>
        <item name="regular_user_color">@color/black</item>
        <item name="fab_tint_background">@color/teal_700</item>
        <item name="fab_border_color">@color/teal_700</item>
        <item name="fab_icon_color">@color/white</item>
    </style>
    <attr name="active_background_color" format="reference" />
    <attr name="inactive_background_color" format="reference" />
    <attr name="fab_tint_background" format="reference" />
    <attr name="fab_border_color" format="reference" />
    <attr name="fab_icon_color" format="reference" />
    <attr name="main_user_color" format="reference" />
    <attr name="regular_user_color" format="reference" />
    <style name="ToolbarTheme" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar">
        <item name="android:textColor">@color/black</item>
    </style>
</resources>

8.2 themes.xml (Dark-Mode)

Listing 23: themes.xml (Dark-Mode)
<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.CrudDiary" parent="Theme.MaterialComponents.DayNight.NoActionBar">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/purple_200</item>
        <item name="colorPrimaryVariant">@color/purple_700</item>
        <item name="colorOnPrimary">@color/black</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">@color/teal_200</item>
        <item name="colorSecondaryVariant">@color/teal_200</item>
        <item name="colorOnSecondary">@color/black</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
        <item name="active_background_color">@color/teal_700</item>
        <item name="inactive_background_color">@color/colorVeryDarkGrey</item>
        <item name="main_user_color">@color/purple_200</item>
        <item name="regular_user_color">@color/black</item>
        <item name="fab_tint_background">@color/teal_200</item>
        <item name="fab_border_color">@color/white</item>
        <item name="fab_icon_color">@color/black</item>
    </style>
    <style name="ToolbarTheme" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar">
        <item name="android:textColor">@color/white</item>
    </style>
</resources>

So we define the three attributes fab_tint_background, fab_border_color and fab_icon_color and for our toolbar menu the style ToolbarTheme and use colours that are favourable for the respective mode (Light/Dark).

8.3 string.xml (English)

Listing 24: string.xml (English)
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">CrudDiary</string>
    <string name="nothing_selected" translatable="false">---</string>
    <string name="male_gender">male</string>
    <string name="female_gender">female</string>
    <string name="diverse_gender">diverse</string>
    <string name="main_menu_title">Home</string>
    <string name="persons_list_menu_title">Persons</string>
    <string name="tags_list_menu_title">Tags</string>
    <string name="diary_entries_list_menu_title">Diary entries</string>
    <string name="genders_list_menu_title">Genders</string>
    <string name="help_menu_title">Help</string>
    <string name="support_section">Support</string>
    <string name="preferences_menu_title">Preferences</string>
    <string name="import_export_menu_title">Import/Export</string>
    <string name="search_view_hint">Search entries</string>
    <string name="fab_add_description">Add entry</string>
    <string name="fab_filter_description">Filter entries</string>
    <string name="fab_csv_description">Export entries to CSV file</string>
</resources>

8.4 string.xml (German)

Listing 25: string.xml (German)
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">CrudDiary</string>
    <string name="male_gender">m\"annlich</string>
    <string name="female_gender">weiblich</string>
    <string name="diverse_gender">divers</string>
    <string name="main_menu_title">\"Ubersicht</string>
    <string name="persons_list_menu_title">Personen</string>
    <string name="tags_list_menu_title">Schlagw\"orter</string>
    <string name="diary_entries_list_menu_title">Tagebucheintr\"age</string>
    <string name="genders_list_menu_title">Geschlechter</string>
    <string name="help_menu_title">Hilfe</string>
    <string name="support_section">Support</string>
    <string name="preferences_menu_title">Einstellungen</string>
    <string name="import_export_menu_title">Import/Export</string>
    <string name="search_view_hint">Eintr\"age durchsuchen</string>
    <string name="fab_add_description">Eintrag hinzuf\"ugen</string>
    <string name="fab_filter_description">Liste filtern</string>
    <string name="fab_csv_description">Liste in CSV-Datei exportieren</string>
</resources>

8.5 activity_main.xml

Listing 26: activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:id="@+id/mainView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/appBarLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar">
            <com.google.android.material.appbar.MaterialToolbar
                android:id="@+id/toolbar"
                app:theme="@style/ToolbarTheme"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_scrollFlags="noScroll" />
        </com.google.android.material.appbar.AppBarLayout>
        <androidx.core.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fillViewport="true"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
            <androidx.fragment.app.FragmentContainerView
                android:id="@+id/nav_host_fragment"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:padding="16dp"
                app:defaultNavHost="true"
                app:navGraph="@navigation/nav_graph" />
        </androidx.core.widget.NestedScrollView>
        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/add_floating_action_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_margin="16dp"
            android:visibility="gone"
            tools:visibility="visible"
            android:backgroundTint="?attr/fab_tint_background"
            app:tint="?attr/fab_icon_color"
            app:backgroundTint="?attr/fab_border_color"
            app:borderWidth="3dp"
            android:contentDescription="@string/fab_add_description"
            android:src="@android:drawable/ic_input_add" />
        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/filter_floating_action_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_anchor="@id/add_floating_action_button"
            app:layout_anchorGravity="start|bottom"
            android:layout_marginEnd="80dp"
            android:layout_marginBottom="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginStart="16dp"
            android:visibility="gone"
            tools:visibility="visible"
            android:backgroundTint="?attr/fab_tint_background"
            app:tint="?attr/fab_icon_color"
            app:backgroundTint="?attr/fab_border_color"
            app:borderWidth="3dp"
            android:contentDescription="@string/fab_filter_description"
            android:src="@android:drawable/ic_search_category_default" />
        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/csv_floating_action_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_anchor="@id/filter_floating_action_button"
            app:layout_anchorGravity="start|bottom"
            android:layout_marginEnd="144dp"
            android:layout_marginBottom="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginStart="16dp"
            android:visibility="gone"
            tools:visibility="visible"
            android:backgroundTint="?attr/fab_tint_background"
            app:tint="?attr/fab_icon_color"
            app:backgroundTint="?attr/fab_border_color"
            app:borderWidth="3dp"
            android:contentDescription="@string/fab_csv_description"
            android:src="@drawable/baseline_save_24" />
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
    <com.google.android.material.navigation.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:menu="@menu/menu_main" />
</androidx.drawerlayout.widget.DrawerLayout>

We use the element DrawerLayout and NavigationView to realise a fold-out menu at the upper left edge of the screen. Using AppBarLayout and MaterialToolbar, we implement a tool menu (Settings, Import/Export and Help) at the top right of the screen. This toolbar menu is packed together with three floating action buttons (FAB) and a FragmentContainerView within the CoordinatorLayout element to control the animation of these components. Within the FragmentContainerView, our individual fragments will be displayed later. In order for the scrolling to work properly, this element is encapsulated in a NestedScrollView. This code is almost entirely based on the example code from the Head First Android Development book. The three FABs are set to invisible by default (android:visibility=”gone”) and are only shown in certain places in the UI using code.

Next, we need to enable the navigation component of Android Jetpack by extending our app build.xml file. To do this, insert the corresponding line:

8.6 build.xml (App)

Listing 27: build.xml
dependencies {
...
 kapt "androidx.room:room-compiler:$room_version"
 implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" // added line
 testImplementation junit:junit:4.13.2’
...
}

We also need to create a navigation graph. Add this by right-clicking on the res folder and selecting ’New - Android Resource File’ and give it the name nav_graph and select the option ’Navigation’ as the resource type.

8.7 nav_graph.xml

Listing 28: nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_graph"
    app:startDestination="@id/homeFragment">
    <fragment
        android:id="@+id/homeFragment"
        android:name="app.gedama.tutorial.cruddiary.fragment.HomeFragment"
        android:label="fragment_home" />
</navigation>

Next, we need to create the missing fragment fragment_home.xml, in which we simply display a dummy text in a TextView for now:

8.8 fragment_home.xml

Listing 29: fragment_home.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Home" />
</androidx.constraintlayout.widget.ConstraintLayout>

8.9 MainActivity.kt

In MainActivity.kt we now initialise and integrate the previously defined menus as well as the NavigationHost, which is responsible for exchanging the fragments in main_activity.xml according to the user’s navigation. Note also that we set the variable appCtx of our global utitlity class GlobalAppStore so that we can reference the application context from any class in our app.

Listing 30: MainActivity.kt
package app.gedama.tutorial.cruddiary
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.ui.AppBarConfiguration
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.drawerlayout.widget.DrawerLayout
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.NavigationUI
import androidx.navigation.ui.onNavDestinationSelected
import androidx.navigation.ui.setupWithNavController
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.navigation.NavigationView
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        GlobalAppStore.appCtx = applicationContext
        val toolbar = findViewById<MaterialToolbar>(R.id.toolbar)
        setSupportActionBar(toolbar)
        val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
        val navController = navHostFragment.navController
        val drawer = findViewById<DrawerLayout>(R.id.drawer_layout)
        val builder = AppBarConfiguration.Builder(navController.graph)
        builder.setOpenableLayout(drawer)
        val appBarConfiguration = builder.build()
        toolbar.setupWithNavController(navController, appBarConfiguration)
        val navView = findViewById<NavigationView>(R.id.nav_view)
        NavigationUI.setupWithNavController(navView, navController)
    }
    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.menu_toolbar, menu)
        return super.onCreateOptionsMenu(menu)
    }
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        val navController = findNavController(R.id.nav_host_fragment)
        return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
    }
}

With all these files we can now compile our code and execute it either on a virtual or a real device. The start page with the fragment fragment_home.xml does not offer much at the moment (see Figure 8.1).

Home screen
Figure 8.1: Home screen

If you tap the menu icon (burger symbol) at the top left, the menu items defined in menu_main.xml are displayed (see Figure 8.2).

Main menu
Figure 8.2: Main menu

If you tap on the toolbar menu icon (four vertical dots) in the top right-hand corner, the menu item Help defined in toolbar_menu.xml is displayed (see Figure 8.3) - the other menu items are hidden for the time being, we will deal with their display later.

Toolbar menu
Figure 8.3: Toolbar menu