Adding a New Fragment and ViewModel

The first step is to add a ViewModel to store the List of Crime objects you will eventually display on the screen. As you learned in Chapter 4, the ViewModel class is part of the lifecycle-viewmodel-ktx library. So begin by adding the lifecycle-viewmodel-ktx dependency to your app/build.gradle file (that is, the build.gradle file labeled Module: CriminalIntent.app).

Listing 10.1  Adding lifecycle-viewmodel-ktx dependency (app/build.gradle)

dependencies {
    ...
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    implementation 'androidx.fragment:fragment-ktx:1.4.1'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
    ...
}

Do not forget to sync your Gradle files after making this change.

Next, create a Kotlin class called CrimeListViewModel. Update the new CrimeListViewModel class to extend from ViewModel. Add a property to store a list of Crimes. In the init block, populate the list with dummy data.

Listing 10.2  Generating crimes (CrimeListViewModel.kt)

class CrimeListViewModel : ViewModel() {

    val crimes = mutableListOf<Crime>()

    init {
        for (i in 0 until 100) {
            val crime = Crime(
                id = UUID.randomUUID(),
                title ="Crime #$i",
                date = Date(),
                isSolved = i % 2 == 0
            )

            crimes += crime
        }
    }
}

Eventually, the List will contain user-created Crimes that can be saved and reloaded. For now, you populate the List with 100 boring Crime objects.

The CrimeListViewModel is not a solution for long-term storage of data, but it does encapsulate all the data necessary to populate CrimeListFragment’s view. In Chapter 12, you will learn more about long-term data storage when you update CriminalIntent to store the crime list in a database.

Next, create the CrimeListFragment class and associate it with CrimeListViewModel. Make it a subclass of androidx.fragment.app.Fragment.

Listing 10.3  Implementing CrimeListFragment (CrimeListFragment.kt)

private const val TAG = "CrimeListFragment"

class CrimeListFragment : Fragment() {

    private val crimeListViewModel: CrimeListViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(TAG, "Total crimes: ${crimeListViewModel.crimes.size}")
    }
}

For now, CrimeListFragment is an empty shell of a fragment. It does not even have a UI to display; it just logs the number of crimes found in CrimeListViewModel. You will flesh the fragment out later in this chapter.

ViewModel lifecycle with fragments

In Chapter 4, you learned about the ViewModel lifecycle when used with an activity. This lifecycle is slightly different when the ViewModel is used with a fragment. It still only has two states, created or destroyed/nonexistent, but it is now tied to the lifecycle of the fragment instead of the activity.

The ViewModel will remain active as long as the fragment’s view is onscreen. This means the ViewModel will persist across rotation (even though the fragment instance will not) and be accessible to the new fragment instance.

The ViewModel will be destroyed when the fragment is destroyed. This can happen when the hosting activity replaces the fragment with a different one. Even though the same activity is on the screen, both the fragment and its associated ViewModel will be destroyed, since they are no longer needed.

One special case is when you add the fragment transaction to the back stack. When the activity replaces the current fragment with a different one, if the transaction is added to the back stack, the fragment instance and its ViewModel will not be destroyed. This maintains your state: If the user presses the Back button, the fragment transaction is reversed. The original fragment instance is put back on the screen, and all the data in the ViewModel is preserved.

Next, update activity_main.xml to host an instance of CrimeListFragment instead of CrimeDetailFragment.

Listing 10.4  Adding CrimeListFragment (activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/fragment_container"
    android:name="com.bignerdranch.android.criminalintent.CrimeDetailFragment"
    android:name="com.bignerdranch.android.criminalintent.CrimeListFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" />

For now, you have hardcoded MainActivity to always display a CrimeListFragment. In Chapter 13, you will update MainActivity to use the Fragment Navigation library to navigate between CrimeListFragment and CrimeDetailFragment as the user moves through the app.

Run CriminalIntent, and you will see MainActivity’s FragmentContainerView hosting an empty CrimeListFragment, as shown in Figure 10.2.

Figure 10.2  Blank MainActivity screen

Blank MainActivity screen

Search the Logcat output for CrimeListFragment. You will see a log statement showing the total number of crimes:

    2022-02-25 15:19:39.950 26140-26140/com.bignerdranch.android.criminalintent
        D/CrimeListFragment: Total crimes: 100
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset