Along with the book, we will be creating the Criminal Intent app in class.

Description

Screenshot of Criminal Intent
Figure 1. Criminal Intent

To get to this point, we will use fragments as UI components. Fragments allow us to create resuable views that may or may not be mixed together in various ways depending on the current orientation, device, or context of use.

Fragment Example
Figure 2. Fragment Example

For today, we will just implement an editor for a particular crime.

Crime Editor
Figure 3. Crime Editor

Here is the way in wich the crime editor will be wired to the MainActivity and controller data for this application.

Embedding CrimeFragment
Figure 4. Embedding CrimeFragment

And here is an object diagram showing the MVC relationship for the components.

MVC Object Diagram
Figure 5. MVC Object Diagram

Source

Crime.kt
package com.example.criminalintent

import java.util.*

data class Crime(val id: UUID = UUID.randomUUID(),
                 var title: String = "",
                 var date: Date = Date(),
                 var isSolved: Boolean = false) {

}
CrimeFragment.kt
package com.example.criminalintent

import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.CheckBox
import android.widget.EditText


// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"

const val TAG = "CrimeFragment"

/**
 * A simple [Fragment] subclass.
 * Activities that contain this fragment must implement the
 * [CrimeFragment.OnFragmentInteractionListener] interface
 * to handle interaction events.
 * Use the [CrimeFragment.newInstance] factory method to
 * create an instance of this fragment.
 *
 */
class CrimeFragment : Fragment() {
    // TODO: Rename and change types of parameters
    private var param1: String? = null
    private var param2: String? = null
    private var listener: OnFragmentInteractionListener? = null

    private lateinit var titleField: EditText (3)
    private lateinit var dateButton: Button
    private lateinit var solvedCheckbox: CheckBox
    private lateinit var logCrimeButton: Button
    private lateinit var crime: Crime

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
            param2 = it.getString(ARG_PARAM2)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        val view = inflater.inflate(R.layout.fragment_crime, container, false)

        dateButton = view.findViewById(R.id.crime_date)       (3)
        solvedCheckbox = view.findViewById(R.id.crime_solved)
        titleField = view.findViewById(R.id.crime_title)
        logCrimeButton = view.findViewById(R.id.log_crime)
        crime = Crime()

        logCrimeButton.setOnClickListener {
            Log.d(TAG, crime.toString())
        }

        dateButton.apply {
            text = crime.date.toString()
            isEnabled = false
        }

        solvedCheckbox.apply {
            setOnCheckedChangeListener { _, isChecked ->
                crime.isSolved = isChecked
            }
        }

        return view
    }

    override fun onStart() { (1)
        super.onStart()

        val titleWatcher = object: TextWatcher { (2)
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
                // blank
            }

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                crime.title = s.toString()
            }

            override fun afterTextChanged(s: Editable?) {
                // blank
            }
        }
        titleField.addTextChangedListener(titleWatcher)
    }

    override fun onDetach() {
        super.onDetach()
        listener = null
    }

    /**
     * This interface must be implemented by activities that contain this
     * fragment to allow an interaction in this fragment to be communicated
     * to the activity and potentially other fragments contained in that
     * activity.
     *
     *
     * See the Android Training lesson [Communicating with Other Fragments]
     * (http://developer.android.com/training/basics/fragments/communicating.html)
     * for more information.
     */
    interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        fun onFragmentInteraction(uri: Uri)
    }

    companion object {
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment CrimeFragment.
         */
        // TODO: Rename and change types and number of parameters
        @JvmStatic
        fun newInstance(param1: String, param2: String) =
            CrimeFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                    putString(ARG_PARAM2, param2)
                }
            }
    }
}
1 Note that the generator will produce a different method and we are following the book by using onStart. If you allow Android Studio to generate this file, it will contain an onAttach override instead.
2 This TextWatcher will be updated every time a character is changed in the text field.
3 Note that since Fragment is not a View, View does not exist until onCreateView. We are creating the view as the first order of business in that method.
fragment_crime.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             xmlns:tools="http://schemas.android.com/tools"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             tools:context=".CrimeFragment">

    <!-- TODO: Update blank fragment layout -->
    <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"
                  android:orientation="vertical">
        <TextView android:layout_width="match_parent" android:layout_height="wrap_content"
                  android:text="@string/crime_title_label"/>
        <EditText android:layout_width="match_parent" android:layout_height="wrap_content"
                  android:hint="@string/crime_title_hint" android:id="@+id/crime_title"/>
        <TextView android:layout_width="match_parent" android:layout_height="wrap_content"
                  android:text="@string/crime_details_label"/>
        <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/crime_date"
                android:text="Wed Nov 14 11:56 EST 2018"/>
        <CheckBox android:layout_width="match_parent" android:layout_height="wrap_content"
                  android:text="@string/crime_solved_label" android:id="@+id/crime_solved"/>

        <Button android:layout_width="match_parent" android:layout_height="wrap_content"
        android:id="@+id/log_crime" android:text="Log current crime object"/>
    </LinearLayout>

</FrameLayout>
strings.xml
<resources>
    <string name="app_name">CriminalIntent</string>

    <!-- TODO: Remove or change this placeholder text -->
    <string name="hello_blank_fragment">Hello blank fragment</string>
    <string name="crime_title_label">Title</string>
    <string name="crime_title_hint">Enter a title for the crime.</string>
    <string name="crime_details_label">Details</string>
    <string name="crime_solved_label">Solved</string>
</resources>
MainActivity.kt
package com.example.criminalintent

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.FrameLayout

class MainActivity : AppCompatActivity() {

    val fragmentContainer: FrameLayout by lazy { findViewById<FrameLayout>(R.id.fragment_container) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val currentFragment = supportFragmentManager.findFragmentById(R.id.fragment_container)
        if (currentFragment == null) {
            val fragment = CrimeFragment()
            supportFragmentManager
                .beginTransaction()
                .add(R.id.fragment_container, fragment)
                .commit()
        }
    }
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_height="match_parent"
             android:layout_width="match_parent"
           android:id="@+id/fragment_container" >

</FrameLayout>