Database

database/CrimeDatabase.kt
package edu.ius.c490.criminalintent.database

import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import edu.ius.c490.criminalintent.Crime

@Database(entities = [Crime::class], version = 1)
@TypeConverters(CrimeTypeConverters::class)
abstract class CrimeDatabase : RoomDatabase() {
    abstract fun crimeDao(): CrimeDao
}
database/CrimeDao.kt
package edu.ius.c490.criminalintent.database

import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Query
import edu.ius.c490.criminalintent.Crime
import java.util.*

@Dao
interface CrimeDao {
    @Query("select * from crime order by date asc")
    fun getCrimes(): LiveData<List<Crime>>

    @Query("select * from crime where id=(:id)")
    fun getCrime(id: UUID): LiveData<Crime?>
}
database/CrimeTypeConverters.kt
package edu.ius.c490.criminalintent.database

import androidx.room.TypeConverter
import java.util.*
import kotlin.system.measureTimeMillis

class CrimeTypeConverters {
    @TypeConverter
    fun fromDate(date: Date?): Long? {
        return date?.time
    }

    @TypeConverter
    fun toDate(milsSinceEpoch: Long?): Date? {
        return milsSinceEpoch?.let {
            Date(it)
        }
    }

    @TypeConverter
    fun fromUUID(uuid: UUID?): String? {
        return uuid.toString()
    }

    @TypeConverter
    fun toUUID(uuid: String?): UUID? {
        return UUID.fromString(uuid)
    }
}

CriminalIntent

CrimeRepository.kt
package edu.ius.c490.criminalintent

import android.content.Context
import androidx.room.Room
import edu.ius.c490.criminalintent.database.CrimeDatabase
import java.lang.IllegalStateException
import java.util.*

private const val DATABASE_NAME = "crime-database"

class CrimeRepository private constructor(context: Context) {
    companion object {
        private var instance: CrimeRepository? = null

        fun initialize(context: Context) {
            if (instance == null) {
                instance = CrimeRepository(context)
            }
        }

        fun get(): CrimeRepository {
            return instance ?: throw IllegalStateException("CrimeRepository must be initialized!")
        }
    }

    private val database: CrimeDatabase = Room.databaseBuilder(
            context.applicationContext,
            CrimeDatabase::class.java,
            DATABASE_NAME
        ).build()

    private val crimeDao = database.crimeDao()

    fun getCrimes() = crimeDao.getCrimes()

    fun getCrime(id: UUID) = crimeDao.getCrime(id)
}
Crime.kt
package edu.ius.c490.criminalintent

import androidx.room.Entity
import androidx.room.PrimaryKey
import java.util.*

@Entity
data class Crime(@PrimaryKey var id: UUID = UUID.randomUUID(),
                 var title: String = "Crime",
                 var date: Date = Date(),
                 var solved: Boolean = false)
CrimeListViewModel.kt
package edu.ius.c490.criminalintent

import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel

class CrimeListViewModel : ViewModel() {
    private val crimeRepository = CrimeRepository.get()
    val crimes: LiveData<List<Crime>> = crimeRepository.getCrimes()
}
CrimeFragment.kt
package edu.ius.c490.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"
private 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 crimeTitle: EditText
    private lateinit var crimeDate: Button
    private lateinit var crimeSolved: CheckBox
    private lateinit var logCrime: 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)
        }
        Log.d(TAG, "onCreate")
    }

    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)

        Log.d(TAG, "onCreateView")

        crime = Crime()

        crimeTitle = view.findViewById(R.id.crime_title)
        crimeDate = view.findViewById(R.id.crime_date)
        crimeSolved = view.findViewById(R.id.crime_solved)
        logCrime = view.findViewById(R.id.log_crime)

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

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

        crimeSolved.apply {
            setOnCheckedChangeListener { _, isChecked ->
                crime.solved = isChecked
            }
        }

        val textWatcher = object : TextWatcher {
            override fun afterTextChanged(p0: Editable?) {
                // do nothing
            }

            override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
                // do nothing
            }

            override fun onTextChanged(sequence: CharSequence?, p1: Int, p2: Int, p3: Int) {
                crime.title = sequence.toString()
            }

        }
        crimeTitle.addTextChangedListener(textWatcher)

        return view
    }

    override fun onStart() {
        super.onStart()

        Log.d(TAG, "onStart")

    }

    override fun onDetach() {
        super.onDetach()
        listener = null
        Log.d(TAG, "onDatach")
    }

    /**
     * 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)
                }
            }
    }
}
CrimeListFragment.kt
package edu.ius.c490.criminalintent

import androidx.lifecycle.ViewModelProviders
import android.os.Bundle
import android.text.format.DateFormat
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.RadioButton
import android.widget.TextView
import android.widget.Toast
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import kotlinx.android.synthetic.main.fragment_crime.*

private const val TAG = "CrimeListFragment"

class CrimeListFragment : Fragment() {

    companion object {
        fun newInstance() = CrimeListFragment()
    }

    private val viewModel: CrimeListViewModel by lazy {
        ViewModelProviders.of(this).get(CrimeListViewModel::class.java)
    }
    private lateinit var crimeRecyclerView: RecyclerView
    private var adapter: CrimeAdapter? = CrimeAdapter(emptyList())

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.crime_list_fragment, container, false)


        crimeRecyclerView = view.findViewById(R.id.crime_recycler_view)
        crimeRecyclerView.layoutManager = LinearLayoutManager(context)

        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewModel.crimes.observe(viewLifecycleOwner,
            Observer { crimes ->
                updateUI(crimes)
            }
        )
    }

    private fun updateUI(crimes: List<Crime>) {
        Log.d(TAG, "Total crimes: ${crimes.size}")
        adapter = CrimeAdapter(crimes)
        crimeRecyclerView.adapter = adapter
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
    }

    private inner class CrimeHolder(view: View) : ViewHolder(view), View.OnClickListener {
        override fun onClick(p0: View?) {
            Toast.makeText(context, "You clicked ${crime.title}!", Toast.LENGTH_SHORT).show()
            Log.d(TAG, "$crime")
        }

        init {
            itemView.setOnClickListener(this)
        }

        private lateinit var crime: Crime
        private val titleTextView: TextView = itemView.findViewById(R.id.crime_title)
        private val dateTextView: TextView = itemView.findViewById(R.id.crime_date)
        private val isSolvedCheckBox: CheckBox = itemView.findViewById(R.id.crime_solved)
        private lateinit var testRadio: RadioButton

        fun bind(crime: Crime) {
            this.crime = crime
            titleTextView.text = crime.title
            dateTextView.text = DateFormat.format("HH:mm 'on' yyyy.MM.dd", crime.date)
            isSolvedCheckBox.isChecked = crime.solved
            isSolvedCheckBox.setOnCheckedChangeListener { _, isChecked ->
                crime.solved = isChecked
                Log.d(TAG, "$crime")
            }
        }
    }

    private inner class CrimeAdapter(var crimes: List<Crime>) : RecyclerView.Adapter<CrimeHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CrimeHolder {
            val view = layoutInflater.inflate(R.layout.list_item_crime, parent, false)
            return CrimeHolder(view)
        }

        override fun getItemCount(): Int = crimes.size

        override fun onBindViewHolder(holder: CrimeHolder, position: Int) {
            holder.bind(crimes[position])
        }

    }
}
MainActivity.kt
package edu.ius.c490.criminalintent

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

class MainActivity : AppCompatActivity() {

    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 = CrimeListFragment()
            supportFragmentManager
                .beginTransaction()
                .add(R.id.fragment_container, fragment)
                .commit()
        }
    }
}

Manifest/Application

manifests/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="edu.ius.c490.criminalintent">

    <application
        android:name=".CriminalIntentApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
CriminalIntentApplication.kt
package edu.ius.c490.criminalintent

import android.app.Application

class CriminalIntentApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        CrimeRepository.initialize(this)
    }
}