How to Build a CRUD App With Kotlin and Android Studio

Chapter 22 Home Screen

In this chapter we implement our home screen which shows a welcome message and the number of diary entries (see Figure 22.1). Besides, we also add a button at the bottom of the screen and a dummy text as a placeholder for a banner ad: the button will display a rewarded ad and the dummy text view will be replaced by an actual ad banner later in our tutorial.

Home screen showing number of diary entries
Figure 22.1: Home screen showing number of diary entries

First we add a new vector asset called baseline_ads_click_24 to our drawables folder.

Next we need a couple of new string values:

22.1 string.xml (English)

For our user interface, we have to add a couple of string values to our string.xml files:

Listing 156: string.xml (English)
...
    <string name="welcome">Welcome to CRUD-Diary!</string>
    <string name="logo_description">CRUD-Diary Logo</string>
    <string name="home_heading_diary_entries">Diary entries</string>
    <string name="ad_reward_button">Reduce ads</string>
</resources>

22.2 DiaryEntryDao.kt

Update of DiaryEntryDao.kt

Listing 157: DiaryEntryDao.kt
...
    @Query("DELETE FROM diary_entries_tags WHERE entry_id = :id")
    suspend fun deleteAllTagsForEntry(id: Long)
    @Query("SELECT COUNT(entry_id) FROM diary_entries WHERE inactive <= :inactive")
    fun getNumberOfEntries(inactive: Boolean = false): LiveData<Int>
}

Next we extend our HomeViewModel:

22.3 HomeViewModel.kt

Listing 158: HomeViewModel.kt
package app.gedama.tutorial.cruddiary.viewmodel
import androidx.lifecycle.*
import app.gedama.tutorial.cruddiary.dao.DiaryEntryDao
import app.gedama.tutorial.cruddiary.dao.PreferencesDao
class HomeViewModel(diaryEntryDao: DiaryEntryDao, preferencesDao: PreferencesDao) : BaseViewModel() {
    val numberOfDiaryEntries = diaryEntryDao.getNumberOfEntries()
    val preferences = preferencesDao.getPreferences()
    private val _navigateToDiaryEntries = MutableLiveData<Boolean>(false)
    val navigateToDiaryEntries: LiveData<Boolean>
        get() = _navigateToDiaryEntries
    fun onEntriesClicked() {
        _navigateToDiaryEntries.value = true
    }
    fun onDiaryEntriesNavigated() {
        _navigateToDiaryEntries.value = false
    }
}
class HomeViewModelFactory(private val diaryEntryDao: DiaryEntryDao, private val preferencesDao: PreferencesDao): ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(HomeViewModel::class.java)) {
            return HomeViewModel(diaryEntryDao, preferencesDao) as T
        }
        throw java.lang.IllegalArgumentException("Unknown ViewModel")
    }
}

Now we adapt the layout of our home fragment:

22.4 fragment_home.xml

Listing 159: fragment_home.xml
<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="app.gedama.tutorial.cruddiary.fragment.HomeFragment">
    <data>
        <variable
            name="viewModel"
            type="app.gedama.tutorial.cruddiary.viewmodel.HomeViewModel" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <GridLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:rowCount="1"
                android:columnCount="2"
                android:layout_margin="6dp"
                android:orientation="horizontal">
                <TextView
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_gravity="fill_horizontal|start|center_vertical"
                    android:layout_row="0"
                    android:layout_column="0"
                    android:paddingStart="6dp"
                    android:paddingEnd="6dp"
                    android:text="@string/welcome"
                    android:textStyle="bold" />
                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_row="0"
                    android:layout_column="1"
                    android:gravity="end"
                    android:layout_marginEnd="0dp"
                    android:layout_gravity="center_vertical"
                    android:contentDescription="@string/logo_description"
                    android:src="@mipmap/ic_launcher" />
            </GridLayout>
            <androidx.cardview.widget.CardView
                android:id="@+id/cardViewTasks"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="6dp"
                app:cardBackgroundColor="?attr/active_background_color"
                app:cardElevation="4dp"
                app:cardCornerRadius="4dp"
                android:onClick="@{ () -> viewModel.onEntriesClicked() }">
                <TableLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:layout_marginBottom="8dp"
                    android:stretchColumns="*">
                    <TableRow
                        android:layout_width="match_parent"
                        android:layout_height="match_parent">
                        <TextView
                            android:id="@+id/numberOfDiaryEntriesLabel"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:padding="8dp"
                            android:textStyle="bold"
                            android:text="@string/home_heading_diary_entries" />
                        <TextView
                            android:id="@+id/numberOfDiaryEntries"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:padding="8dp"
                            android:layout_gravity="end"
                            android:textStyle="bold"
                            tools:text="12"
                            android:text="@{viewModel.numberOfDiaryEntries.toString()}"/>
                    </TableRow>
                </TableLayout>
            </androidx.cardview.widget.CardView>
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:gravity="bottom|center"
            android:orientation="vertical">
            <com.google.android.material.button.MaterialButton
                android:id="@+id/reward_ad_button"
                android:visibility="visible"
                android:layout_marginBottom="10dp"
                android:text="@string/ad_reward_button"
                tools:visibility="visible"
                app:icon="@drawable/baseline_ads_click_24"
                style="@style/CrudDiary_Button" />
            <TextView
                android:id="@+id/adView"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Banner Ad" />
        </LinearLayout>
    </LinearLayout>
</layout>

22.5 HomeFragment.kt

Listing 160: HomeFragment.kt
...
        viewModel.preferences.observe(viewLifecycleOwner, {
            if (it != null ) {
                // neccesary because at the first run these data aren’t available yet
                val mainUserId = it.mainUserId ?: 1L
                val showInactive = it.showInactive ?: false
                Log.d("HomeViewModel", "Main user ID: $mainUserId")
                GlobalAppStore.mainUserId = mainUserId
                GlobalAppStore.updateShowInactive(showInactive)
                Log.d("HomeViewModel", "Show inactive: $showInactive")
            }
        })
        viewModel.navigateToDiaryEntries.observe(viewLifecycleOwner, { navigate ->
            if ( navigate ) {
                // viewModel.showInterstitialAd(this.requireActivity())
                val action = HomeFragmentDirections
                    .actionHomeFragmentToDiaryEntriesListFragment()
                this.findNavController().navigate(action)
                viewModel.onDiaryEntriesNavigated()
            }
        })
        return view
...

When you install the app to a (virtual) device, you will see our new home screen (see Figure 22.1).

When you tap on the card showing the number of diary entries, you get to the diary entries list.