Project 15: To-Do list app
Complete video course: Udemy
Introduction
To-Do list app is a command-line app that lets users add and list to-dos, which are simple text strings eg. Watch a movie, Clean my room, etc.
The user is presented with a menu containing 3 options: to add a to-do item, list added to-dos, and exit from the program. Upon making a selection, the relevant function(s) get called.
Practical
Boilerplate code:
package main
import (
)
func main() {
}
Let us first import all the packages that we will be using:
import (
"bufio"
"fmt"
"os"
)
Note: The bufio and os packages are imported since we expect to read multi-word inputs from the user.
In the main function, we can start with a banner which is just our program name:
fmt.Println("To-Do list app")
Next, we shall have a loop in which the user will be continually presented with a menu, prompted for a choice, and the relevant functions called.
for {
}
Inside the above loop statement, we start with the menu and reading the user’s choice:
var choice int
fmt.Println("1. Add To-Do item")
fmt.Println("2. List To-Dos")
fmt.Println("3. Exit")
fmt.Print("Enter your choice: ")
_, err := fmt.Scanln(&choice)
if err != nil {
fmt.Println(err)
continue
}
Armed with the user’s choice, we proceed to use a switch…case statement to call the relevant functions (which we will define):
switch choice {
case 1:
addToDo()
case 2:
listToDos()
case 3:
fmt.Println("Exiting ...")
return
default:
fmt.Println("Invalid option! Try again.")
}
In the above snippet, we handle all the 3 possible options, calling the addToDo and listToDos functions in cases 1 and 2 respectively. For case 3 (exiting from the program), we display the exiting message and return from the loop. Finally, we have a default case whose code block will be executed if all other cases don’t check out (if the user provides a number that is neither 1,2, nor 3).
Time to define the missing functions: addToDo and listToDos. Before that, we have to declare a global variable (a variable that can be accessed from any point or function in the program) that will store the to-dos. Add this after the package importation section and before any functions:
var todos []string
We create a slice of strings since we expect multiple items of type string and the exact size is unknown.
Note: Go has the array data type which is a composite data type that enables us to store multiple values of the SAME data type as a single value. Arrays however usually have a fixed size, and that is where the slice comes in. A slice is a flexible, dynamic version of the array that grows and shrinks as needed since it doesn’t have a fixed size.
With our variable declared, we can define the function to add a new to-do item:
func addToDo() {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter the task description: ")
task, err := reader.ReadString('\n')
if err != nil {
fmt.Println(err)
return
}
todos = append(todos, task)
fmt.Println("Task added successfully")
}
In the above function, we prompt the user for the task/to-do’s description, read their input using bufio package, and then add it as an entry to the todos slice using the append function. The append takes in 2 values: the slice to update, and the value to add to the slice, and returns the updated slice which we reassign to the todos variable. We conclude by notifying the user that the task has been added successfully.
The other function, listToDos, is as follows:
func listToDos() {
if len(todos) == 0 {
fmt.Println("No tasks found")
return
}
for i, todo := range todos {
fmt.Printf("%d. %s", i+1, todo)
}
}
First, we check if the size of the todos slice is 0, meaning the slice is empty, and let the user know before returning from the function. Otherwise, we loop through the todos slice using the for statement, using both the index and value returned by the range keyword this time and displaying the to-do items to the user.
Back in the main function and within the main for loop, we can have a blank line after each run of the loop:
fmt.Println()
Complete code:
package main
import (
"bufio"
"fmt"
"os"
)
var todos []string
func addToDo() {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter the task description: ")
task, err := reader.ReadString('\n')
if err != nil {
fmt.Println(err)
return
}
todos = append(todos, task)
fmt.Println("Task added successfully")
}
func listToDos() {
if len(todos) == 0 {
fmt.Println("No tasks found")
return
}
for i, todo := range todos {
fmt.Printf("%d. %s", i+1, todo)
}
}
func main() {
fmt.Println("To-Do list app")
for {
var choice int
fmt.Println("1. Add To-Do item")
fmt.Println("2. List To-Dos")
fmt.Println("3. Exit")
fmt.Print("Enter your choice: ")
_, err := fmt.Scanln(&choice)
if err != nil {
fmt.Println(err)
continue
}
switch choice {
case 1:
addToDo()
case 2:
listToDos()
case 3:
fmt.Println("Exiting ...")
return
default:
fmt.Println("Invalid option! Try again.")
}
fmt.Println()
}
}