
It's been a while since I've been able to do anything on the pomodoro project. In the previous article we added “quiet times” to our app so that we wouldn't get audible alerts during certain time windows.
Now we have an application which will follow this pattern:
This is great, but I have two main niggles that I want to resolve.
This feature was added because I would often forget to turn my pomodoro timer off during meetings, and that would lead to a confusing moment in the call where my machine would declare to the group “Get back to work!”.
Not ideal.
My meetings might be different every day, and doesn't prevent the quite common case of ad-hoc meetings.
A better solution would be to integrate with your calendar of choice to see when your meetings are and put you into a “quiet time” for the duration of the call.
I had a brief look into a Teams integration, but realised quickly that it would require some more dedicated time which I don't have right now, so this will have to wait until the next article I think!
As you may have guessed from the title, this is what I'll be focusing on for the rest of this article. Go has a really awesome framework for creating GUI (graphical user interfaces) which I've been experimenting with a little on another project of mine.
Enter fyne.
Fyne is a tool to create cross-platform apps. It uses material design and one thing I really like about it is that it takes a lot of the thinking out of how something should look. Personally I'm quite good at making things work, but less good at making things look good.
Let's jump straight in. The first thing we need to do is install fyne:
go get fyne.io/fyne/v2This will install fyne and add it as a dependency in our go.mod file. It will also add all of it's dependencies to the go.sum file.
Let's make sure it's working by adding a simple display to our main function:
func main() {
a := app.New()
win := a.NewWindow("Pomodoro")
clock := widget.NewLabel("12:00")
win.SetContent(clock)
win.ShowAndRun()I've just added it to the top of the function and still have the other code below. The code above is fairly easy to read and understand. First we create a new app, and then a new window for the app. The string that we pass to it will be the name of the window, which will show on the top part of the window if there is enough room to display it.
Next we create a new label widget and give it a hard coded value of 12:00. Finally we set the content to be this label and “show and run” our app.
If we run our go app we should see a tiny window appear which looks like this:
It works!
You might be wondering why it is so tiny. Fyne will create a window based on the contents within, so if you have a lot of text it will be bigger. You can also resize this window like a normal application on the computer. Here it is a little bigger so that you can see the title as well:
Fortunately, we've done all the logic required for a count down timer, it's just that we need to display it a little differently now.
I'm going to change the initial value of the clock label to be an empty string, then after I initialise my “timer” I'm going to call a separate function “updateTime” which will be responsible for (you guessed it) updating the time.
// main.go
package main
import (
"fmt"
"os"
"strconv"
"time"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/widget"
)
const defaultWorkDurationInMins = 25
const defaultRestDurationInMins = 5
func main() {
a := app.New()
win := a.NewWindow("Pomodoro")
clock := widget.NewLabel("")
workDurationInMins := defaultWorkDurationInMins
restDurationInMins := defaultRestDurationInMins
if len(os.Args) != 3 {
fmt.Println("Incorrect number of arguments, using default values of 25minutes working and 5minutes resting")
}
if len(os.Args) == 3 {
wdur, err1 := strconv.Atoi(os.Args[1])
rdur, err2 := strconv.Atoi(os.Args[2])
workDurationInMins = wdur
restDurationInMins = rdur
if err1 != nil || err2 != nil {
fmt.Println("Problem setting interval values, using default values of 25minutes working and 5minutes resting")
}
}
t := timer{
start: time.Now(),
inWorkMode: true,
workDuration: workDurationInMins * 60,
restDuration: restDurationInMins * 60,
}
go t.updateTime(clock)
win.SetContent(clock)
win.ShowAndRun()
}Within timer.go I'll create the updateTime function. The contents of this is actually what we already had in the main.go file previously
// timer.go
func (t timer) updateTime(clock *widget.Label) {
prevElapsed := 0
for {
elapsed := t.getElapsedTimeInSeconds()
if elapsed != prevElapsed {
t.printTimeRemaining(elapsed, clock)
prevElapsed = elapsed
if t.shouldSwitchMode(elapsed) {
t.alert()
t.switchMode()
}
}
}
}The main change to get this working with fyne however is within the printTimeRemaining function.
// timer.go
func (t timer) printTimeRemaining(elapsed int, clock *widget.Label) {
timeRemaining := t.getDuration() - elapsed
minutes := timeRemaining / 60
seconds := timeRemaining - minutes*60
updatedTime := fmt.Sprintf("\r%v: %02d:%02d", t.getMode(), minutes, seconds)
clock.SetText(updatedTime)
}The main change we're making here is that we've changed our fmt.Printf to be a fmt.Sprintf and saved it to a variable. This variable is then used to set the text of the clock widget which has had it's pointer value passed down to this function, meaning that it can be updated.
That's it! Let's run our application and see it working:

There are a few future improvements I'd like to look into in future articles. One would be to have some input fields for “quiet times” and another would be to have the application remain visible on the screen somehow regardless of what you're doing.
If you're following along with the pomodoro series, you can find the code at this point in time on GitHub.
Previous item in series:
Learn some Time functions in Go while continuing to build our pomodoro timer

Self taught software developer with 12 years experience excelling at JavaScript/Typescript, React, Node and AWS.
I love learning and teaching and have mentored several junior developers over my career. I find teaching is one of the best ways to solidify your own learning, so in the past few years I've been maintaining a technical blog where I write about some things that I've been learning.
I'm passionate about building a teams culture and processes to make it efficient and satisfying to work in. In many roles I have improved the quality and reliability of the code base by introducing or improving the continuous integration pipeline to include quality gates.