Adding a New Fragment and ViewModel

The first step is to add a new 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-extensions library. So begin by adding the lifecycle-extensions dependency to your app/build.gradle file.

Listing 9.1  Adding lifecycle-extensions dependency (app/build.gradle)

dependencies {
    ...
    implementation 'androidx.appcompat:appcompat:1.1.0-alpha02'
    implementation 'androidx.core:core-ktx:1.1.0-alpha04'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
    ...
}

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

Next, create a new 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 9.2  Generating crimes (CrimeListViewModel.kt)

class CrimeListViewModel : ViewModel() {

    val crimes = mutableListOf<Crime>()

    init {
        for (i in 0 until 100) {
            val crime = Crime()
            crime.title = "Crime #$i"
            crime.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 11, you will learn more about long-term data storage when you update CriminalIntent to store the crime list in a database.

The next step is to add a new CrimeListFragment class and associate it with CrimeListViewModel. Create the CrimeListFragment class and make it a subclass of androidx.fragment.app.Fragment.

Listing 9.3  Implementing CrimeListFragment (CrimeListFragment.kt)

private const val TAG = "CrimeListFragment"

class CrimeListFragment : Fragment() {

    private val crimeListViewModel: CrimeListViewModel by lazy {
        ViewModelProviders.of(this).get(CrimeListViewModel::class.java)
    }

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

    companion object {
        fun newInstance(): CrimeListFragment {
            return CrimeListFragment()
        }
    }
}

For now, CrimeListFragment is an empty shell of a fragment. It logs the number of crimes found in CrimeListViewModel. You will flesh this fragment out later in the chapter.

One good practice to follow is to provide a newInstance(…) function that your activities can call to get an instance of your fragment. This is similar to the newIntent() function you used in GeoQuiz. You will see how to pass data to your fragments in Chapter 12.

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. 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 user presses the Back button to dismiss the screen. It can also happen if 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 MainActivity to host an instance of CrimeListFragment instead of CrimeFragment.

Listing 9.4  Adding CrimeListFragment with a fragment transaction (MainActivity.kt)

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        if (currentFragment == null) {
            val fragment = CrimeFragment() CrimeListFragment.newInstance()
            supportFragmentManager
                .beginTransaction()
                .add(R.id.fragment_container, fragment)
                .commit()
        }
    }
}

For now, you have hardcoded MainActivity to always display a CrimeListFragment. In Chapter 12 you will update MainActivity to swap out CrimeListFragment and CrimeFragment on demand as the user navigates through the app.

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

Figure 9.3  Blank MainActivity screen

Blank MainActivity screen

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

    2019-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
18.117.182.179