App 2: Handling Add (Scala 3 Video)
Next up, the “Add” use case is a little bit more complicated. Going back to our UI, we know that when a user wants to add a new task, they type things like this with the a
command:
a wake up
a brush teeth
a take a long hot shower
So the first thing we need to do is to separate that add command — the letter a
— from the rest of the string. Thanks to the power of Scala’s collections methods, we do this with the drop
method that’s available on all strings:
"a wake up".drop(2) // "wake up"
"a brush teeth".drop(2) // "brush teeth"
"a take a long hot shower".drop(2) // "take a long hot shower"
The
drop
method is available because aString
is also a list, specifically aSeq[Char]
.
This approach seems to work well, giving us a string that we can pass to another function, so that function can add the string to our database. So, looking only at the “add” case, I write this:
case add if add.startsWith("a ") =>
handleAdd(add.drop(2))
handleAdd
So now I need to write a handleAdd
function, and I know three things about it already:
- It’s going to take a
String
as input - It needs to add/insert that task into our database
- Because it’s going to access the database, it needs to return a
Try
Therefore, I can start to sketch its signature like this:
def handleAdd(task: String): Try
And because I wrote the database code earlier, I know that handleAdd
just needs to have a reference to the database object so it can perform the insert. Recall that this function is inside the InputProcessor
class, which I defined to take a Database
reference, like this:
class InputProcessor(db: Database)
So now handleAdd
has access to this db
reference.
This is one situation where it’s acceptable for a function to have a side door: when that function is declared inside a class that has a reference like this. (However, if you don’t like this, there are other ways to write this code to make sure a
Database
reference is passed in through the function’s front door (i.e., its input parameter list)).
Getting back to the handleAdd
function, I know that (a) I want to call the insert
function on the db
reference, and (b) it returns a Try[Unit]
. So now I write handleAdd
like this:
def handleAdd(taskName: String): Try[Unit] =
db.insert(taskName)
At this point, this case
code in the match
expression inside the handleUserInput
function could be the end of the case
:
case add if add.startsWith("a ") =>
handleAdd(add.drop(2))
However, because I know from testing the application that I want to see what my tasks look like after I issue my add command, I know that I also need to run the “view” function now. Therefore, I add it after handleAdd
:
case add if add.startsWith("a ") =>
handleAdd(add.drop(2))
handleView()
And because handleView
returns Try[Unit]
, the return type of this case
matches the return type of handleUserInput
. So now handleUserInput
has these cases in its match
expression:
def handleUserInput(input: String): Try[Unit] = input match
case "q" =>
Try(System.exit(0))
case "h" =>
IOHelper.showHelp()
case "v" | "l" =>
handleView()
case add if add.startsWith("a ") => // <== THE NEW CODE
handleAdd(add.drop(2))
handleView()
// more cases here ...
Update: All of my new videos are now on
LearnScala.dev