Lesson 15 - Diary with a database in Kotlin (finishing)
In the previous lesson, Diary with a database in Kotlin, we programmed a part of a digital diary
using List
. In today's tutorial of our Kotlin course, we're going
to finish it.
Let's extend our Diary
class. We'll add an auxiliary
readLocalDateTime()
method which will ask the user for the date and
time and return a LocalDateTime
instance set to this value. Have a
look at it first, we'll describe it right away:
private fun readLocalDateTime(dateTimeFormatter: DateTimeFormatter): LocalDateTime { try { return LocalDateTime.parse(readLine(), dateTimeFormatter) } catch (e: Exception) { println("Incorrect date format. Please try again.") return readLocalDateTime(dateTimeFormatter) } }
The method returns a LocalDateTime
instance and takes a
formatter as a parameter. In the try
block, we try to parse a date
and time entered by the user using the parse()
method. If any error
occurs due parsing the value (e.g. February 30 or something like that), the
program falls into the catch
block, prints an error message, and
calls the method again. Calling the same method we're into is called
recursions. It's a quite common programmer technique.
You might want to keep this method, it can be useful for entering date and
time in your other console applications. We'll get back to
try
-catch
blocks and talk about them in depth later
once we get to working with files.
We'll add 2 public companion objects (static properties). These will be date and time formatters. We make them static to make them available for other classes in our application as well. Every class can use those fields and we have them all at one place:
companion object { val dateTimeFormatter = DateTimeFormatter.ofPattern("d.M.yyyy H:m") val dateFormatter = DateTimeFormatter.ofPattern("d.M.yyyy") }
The first formatter contains both date and time, the second one contains only
a date. Let's move to the Entry.kt
class and modify the
toString()
method like this:
override fun toString(): String { return "${dateTime.format(Diary.dateTimeFormatter)} $text" }
If we created a formatter instance every time in every Entry
, it
wouldn't be very practical. Using static properties, we got them all at one
place and if we ever decided to change the format, we do it only in the
Diary
class. However, be careful with static members, it's
usually not a good idea to use them.
Let's return to the Diary
class and add 2 methods for retrieving
date and time:
private fun readDateTime(): LocalDateTime { println("Enter the date and time as mm/dd/yyyy hh:mm") return readDateTime(dateTimeFormatter) } private fun readDate(): LocalDate { println("Enter date as mm/dd/yyyy") return readLocalDateTime(dateFormatter).toLocalDate() }
They both use our readLocalDateTime()
method but pass a
different formatter. We'll enter date and time using readDateTime()
and only date using readDate()
. Notice that the second method
returns just LocalDate
. It'll come in handy when filtering entries
by date. We surely won't need printing an entry of a particular hour and minute,
however we'll print entries of a certain date without specifying the time.
Let's add a printEntries()
method that will find entries for the
given day and print them:
fun printEntries(day: LocalDateTime) {
val entries = database.findEntries(day, false)
for (entry in entries) {
println(entry)
}
}
We'll also need a method for prompting the user to enter a new entry and adding it to the database:
fun addEntry() {
val dateTime = readDateTime()
println("Enter the entry text:")
val text = readLine()!!
database.addEntry(dateTime, text)
}
Also, we still have to add methods for searching and deleting entries. The
searching method will return a List
of found entries (we'll only
search by date, the time doesn't matter). We'll prompt the user to enter the
date and pass it to the database. Then we'll print the result.
fun searchEntries() { // Entering the date val dateTime = readDate().atStartOfDay() // Searching for entries val entries = database.findEntries(dateTime, false) // Printing entries if (entries.size > 0) { println("Entries found: ") for (entry in entries) { println(entry) } } else { // Nothing found println("No entries were found.") } }
Deleting entries is trivial:
fun deleteEntries() {
println("Entries with the same exact date and time will be deleted")
val dateTime = readDateTime()
database.deleteEntries(dateTime)
}
Last of all, we'll add a method for printing a home screen, showing the current date and time and entries for today and tomorrow.
fun printHomeScreen() { println() println() println("Welcome to your virtual diary!") val today = LocalDateTime.now() println("Today is: ${LocalDateTime.now().format(dateTimeFormatter)}") println() // printing the home screen println("Today:\n------") printEntries(today) println() println("Tomorrow:\n---------") val tommorow = LocalDateTime.now().plusDays(1) printEntries(tommorow) println() }
Now we can proudly move to Main.kt
and create an instance of the
diary. We'll also add the main loop here including a response to the user's
choice:
fun main(args: Array<String>) { val diary = Diary() val choice = "" // main loop while (choice != "4") { diary.printHomeScreen() println() println("Choose an action:") println("1 - Add an entry") println("2 - Search for entries") println("3 - Delete entries") println("4 - End") choice = readLine()!! println() // reaction to the choice when (choice) { "1" -> diary.addEntry() "2" -> diary.searchEntries() "3" -> diary.deleteEntries() "4" -> println("Press any key to quit the program...") else -> println("Error. Press any key to choose another action.") } } }
The code above isn't complicated at this point since we've already made similar applications in previous lessons. I gave the finalized app to my girlfriend as a present, here's what happened :
Welcome to diary! Today is: 5/12/2016 11:34:55 Today: ----- 5/12/2016 10:00:00 Shopping - Pankrac Arcade 5/12/2016 7:30:00 PM - Pet my Yorkshire Terrier Fred Tomorrow: -------- 5/13/2016 2:00:00 PM Go jogging Choose an action: 1 - Add an entry 2 - Search for entries 3 - Delete entries 4 - End 2 Enter date and time as e.g. [1/13/2016 10:00] 5/15/2016 Found entries: 5/15/2016 9:30:00 Economy exam
Now you know how to use List
and rest assured that it'll suffice
as data storage container for a quite a long time. In conclusion, I'd like to
congratulate you in now being able to create in-memory databases. Use it for
anything you want! You could, for example, use it for the User
class from the Properties in
Kotlin or pretty much anything else. You can store articles, tasks,
elephants, anything you want to manage in the database.
What's up next, you may ask? Next time, in Interfaces in Kotlin, we'll be talking about interfaces. There's still a lot to learn about OOP