Creating a TODO app in Fyne an Go
Tags: go, fyne, todo, desktopgui
Published: 2023-05-21 12:24:51

---
title: Creating a TODO app in Fyne an Go
published: true
description: the usual tutorial on how to use a UI framework, but with Go and Fyne
tags: go, fyne, todo, desktopgui
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/15nm4y0n0elqecx0i81h.png
# Use a ratio of 100:42 for best results.
# published_at: 2023-05-20 20:14 +0000
---
In this small tutorial I will explain something that usually is the exercise that allow you to understand how to use a UI framework (at least in the web world), and we will end up step by step in building a TODO app in [Go](https://go.dev/) using the [Fyne](https://fyne.io/) UI Framework.

I will assume that you have at least some knowledge of programming, but it would also be great if you have some knowledge of **go**, personally I found it the easiest Programming language to learn, you can just find more on how to do that in their [learning](https://go.dev/learn/) resources.

It would also be better if you follow the installing instruction of **fyne** in [here](https://developer.fyne.io/started/).


## What are we going to build
It is simple really, just a small app that will allow you to keep a list of things to do, that you can check off if you once done them.

It should look like this:
![Todo Example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dv65itf96i8h9qlksldc.png)


## Setup Project structure

Go has had a bad past for how everything used to be stored in a GOPATH folder, libs, vendors, your own personal projects, but since v1.14, with the introduction of go modules everything makes more sense and is way easier to understand.

We want to init our project, so create a new folder then init the module

```
$ mkdir todoapp && cd todoapp
$ go mod init todoapp
$ touch main.go
```
Some people suggest you to call the module with the url of the github repo you will use to version control it, but it does not really matter if you are not planning to use it elsewhere but in this binary.

open this folder in your favourite code editor and drop this on your `main.go`

```go
package main

import (
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/widget"
)

func main() {
	a := app.New()
	w := a.NewWindow("TODO App")

	w.SetContent(widget.NewLabel("TODOs will go here"))
	w.ShowAndRun()
}

```

Now before running it let's reference fyne and tidy the go modules

```
$ go get fyne.io/fyne/v2@latest
$ go mod tidy
```

After that is done, let's try to run it

```
$ go run .
```

You should see somewhere in your Desktop a small application window that looks like this:
![Small Hello world](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rcmc6uxc3j4rfsiw8bpt.png)

Nothing much to look at, but if you reached this point you are pretty much all set and from now on it will all be great.

You can now do 2 things.

1. Create the models for your app to show
2. Design the UI

I tend to create (usually with TDD) the models the app will show first, so I know what I need to bind in the view, especially in this case where we only have 1 model (`Todo`) I guess we will be all set quite soon with that.

## Todo Model
I would make a folder called `models` and drop in 2 files
```
$ ls models   

todo.go
todo_test.go

```

I won't go too much into the testing otherwise we will just take forever, so I will just show what todo.go declares
```go
package models

import "fmt"

type Todo struct {
	Description string
	Done        bool
}

func NewTodo(description string) Todo {
	return Todo{description, false}
}
```
by default the constructor will set the `Done` property as `false`.
I also added a `func String() string` function to implement the [Stringer](https://pkg.go.dev/fmt#Stringer) interface.
So I can stringify my todo if I wanted to
```go
func (t Todo) String() string {
	return fmt.Sprintf("%s  - %t", t.Description, t.Done)
}
```

Now let's try to show one in our fyne app, but first maybe, let's make that window a bit bigger

```go
func main() {
	a := app.New()
	w := a.NewWindow("TODO App")
        
        // ADDING THIS HERE
	w.Resize(fyne.NewSize(300, 400))
```
Now it should look more like this
![Bigger app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8nvatdiifs0uit1xt8ng.png)

great, now let's create a TODO and show it in the same window.

```go
	w.Resize(fyne.NewSize(300, 400))

	t := models.NewTodo("Show this on the window")

	w.SetContent(widget.NewLabel(t.String()))
```

This is what we get
![Stringified todo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lb545fu8sok7y0jr074x.png)


Brilliant.

Now it is time to create the actual Interface.

## Ux Design

Fyne, like many other Desktop UI frameworks has layouts that can be contained to define how the widgets and items will be positioned around the available space in the window.

There are loads and they are all showcased in [here](https://developer.fyne.io/explore/layouts) and in the Containers/Layouts section.


Let's try one, let's push that todo in the center.

```go
// make sure you import the right one
import "fyne.io/fyne/v2/container"
// this ↑

        w.SetContent(
		container.NewCenter(
			widget.NewLabel(t.String()),
		),
	)
	w.ShowAndRun()
```

in the api examples you can see that this can also be achieved with this form
```go
	w.SetContent(
		container.New(
			layout.NewCenterLayout(),
			widget.NewLabel(t.String()),
		),
	)
	w.ShowAndRun()
```

Which I personally dislike, The first one is syntactic sugar for the second one.

Anyway the app will look like this now:
![Centered](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z9a985zihqwfminmp4eo.png)

Nice! but we want one Text entry and a button on the side, to input and add the todo to a list don't we? Well let's combine stuff and use the Border layout.

```go
	w.SetContent(
		container.NewBorder(
			nil, // TOP of the container

			// this will be a the BOTTOM of the container
			widget.NewButton("Add", func() { fmt.Println("Add was clicked!") }),
			
			nil, // Right
			nil, // Left

			// the rest will take all the rest of the space
			container.NewCenter(
				widget.NewLabel(t.String()),
			),
		),
	)
	w.ShowAndRun()
```
This will add a button at the bottom and on click it will print on the console standard output "Add w as clicked!"

This is what it looks like:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m3gsojj8jscibps55eof.png)

Now let's add the entry at the side of the button, in another container type, HBox, this will stack the item horizontally

```go
	w.SetContent(
		container.NewBorder(
			nil, // TOP of the container

			container.NewHBox(
				widget.NewEntry(),
				widget.NewButton("Add", func() { fmt.Println("Add was clicked!") }),
			),

			nil, // Right
			nil, // Left

			// the rest will take all the rest of the space
			container.NewCenter(
				widget.NewLabel(t.String()),
			),
		),
	)
	w.ShowAndRun()
```
![HBox](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/soklzks50s1acggtijwd.png)
But unfortunately it does not look right, we want them to take all of the space available.

We can try a few more:
```go
			container.NewGridWithColumns(
				2,
				widget.NewEntry(),
				widget.NewButton("Add", func() { fmt.Println("Add was clicked!") }),
			),
```
will look like this
![grid](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4oqgc7q7p7fmddi32p2x.png)

or
```go
			container.NewBorder(
				nil, // TOP
				nil, // BOTTOM
				nil, // Left
				// RIGHT ↓
				widget.NewButton("Add", func() { fmt.Println("Add was clicked!") }),
				// take the rest of the space
				widget.NewEntry(),
			),
```

Nesting another border in the border bottom:
![Nested borders](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ejfasxy6gabgj19dkqo9.png)

It does not really matter, it is a matter of personal preference but I will go with this last one.

## Cleanup and Todo Creation
We now have designed our UI will look like, but let's try to clean up the code as the container tree is quite messy already.

I will move the button and text entry creation before the `.SetContent` call like so:

```go
	newtodoDescTxt := widget.NewEntry()
	newtodoDescTxt.PlaceHolder = "New Todo Description..."
	addBtn := widget.NewButton("Add", func() { fmt.Println("Add was clicked!") })

	w.SetContent(
		container.NewBorder(
			nil, // TOP of the container

			container.NewBorder(
				nil, // TOP
				nil, // BOTTOM
				nil, // Left
				// RIGHT ↓
				addBtn,
				// take the rest of the space
				newtodoDescTxt,
			),
```

declaring those widget before will allow us to modify properties and add handlers to them before getting them into the actual content tree.

In this example I added a placeholder to the entry text so it is clearer what it is for:
![desc placeholder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j02in9j9wkzqi6q5cycd.png)

Now let's make the `"Add"` button do something, and even disable it if the text is empty or too short.
```go
	newtodoDescTxt := widget.NewEntry()
	newtodoDescTxt.PlaceHolder = "New Todo Description..."
	addBtn := widget.NewButton("Add", func() { fmt.Println("Add was clicked!") })
	addBtn.Disable()

	newtodoDescTxt.OnChanged = func(s string) {
		addBtn.Disable()

		if len(s) >= 3 {
			addBtn.Enable()
		}
	}
```
this will disable the button if the text length is less than 3 character:

![disabled btn](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mvhi2hs5hh81pcz9v6yi.png)

and enable it otherwise
![enabled](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4chchb9q4q9ag49rge1b.png)

Great! Now let's try to build the last piece of the UX we are missing, the List of todos.

## Lists in Fyne
There are 2 types of List widget you can use for this purpose, the first one is a static type of list, the second is one which content is linked to the data it will show.

[Simple List](https://developer.fyne.io/widget/list) has a simple API.

```go
			widget.NewList(
				// func that returns the number of items in the list
				func() int {
					return len(data)
				},
				// func that returns the component structure of the List Item
				func() fyne.CanvasObject {
					return widget.NewLabel("template")
				},
				// func that is called for each item in the list and allows
				// you to show the content on the previously defined ui structure
				func(i widget.ListItemID, o fyne.CanvasObject) {
					o.(*widget.Label).SetText(data[i])
				}),
```

in our particular example we want each list item to have a label for the Todo description and a checkbox to show whether the todo is marked as done or not.

Just to try out what it looks like let's create this `data` as a slice of Todos.

```go
	data := []models.Todo{
		models.NewTodo("Some stuff"),
		models.NewTodo("Some more stuff"),
		models.NewTodo("Some other things"),
	}

// then on the last func of list we just replace `data[i]` with

				func(i widget.ListItemID, o fyne.CanvasObject) {
					o.(*widget.Label).SetText(data[i].Description)
				}),
```
and it will look like this:
![Simple List](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xhpkjsi59o599zo1k3st.png)

as you can see there we are getting the `o` `CanvasObject` and type casting it to a `*widget.Label` that is because we know that the function before creates that particular widget, as I said before though we need a [Label](https://developer.fyne.io/widget/label) and a [Checkbox](https://developer.fyne.io/widget/choices), they need to go in a container also, so we can kind of do the same with did with the bottom bar and space them so the label takes most of the space.

```go
				func() fyne.CanvasObject {
					return container.NewBorder(
						nil, nil, nil,
						// left of the border
						widget.NewCheck("", func(b bool) {}),
						// takes the rest of the space
						widget.NewLabel(""),
					)
				},
```

Something like this.

But unfortunately this will throw a compile time as the `o CanvasObject` is not a `*widget.Label` anymore.

We need to cast it to a `Container`, then get the widgets nested within using indexes, and cast them all to what we know they are (as we defined them we should know).
```go
				func(i widget.ListItemID, o fyne.CanvasObject) {
					ctr, _ := o.(*fyne.Container)
					// ideally we should check `ok` for each one of those casting
					// but we know that they are those types for sure
					l := ctr.Objects[0].(*widget.Label)
					c := ctr.Objects[1].(*widget.Check)
					l.SetText(data[i].Description)
					c.SetChecked(data[i].Done)
				}),
```
and this is what it looks like now

![with box](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z8u28i0pb636mkul89h9.png)

Personally I find that casting thing quite weird in the API and getting the order of the components within a container is a bit hit and miss, might need to try compile a couple of time to see whether `[1]` is `Label` or `Check` for real.

Anyway we got there but if we add another todo to that `data` the list won't reflect the changes, as we are just using a static simple list, if we want to make it dynamic, and we need to for our app, we need to use `binding`.

## Binding and Dynamic Lists
Binding is explained in the [docs](https://developer.fyne.io/binding/list) quite well for simple types, but not at all for Struct types, which is what annoyed me the first time I tried fyne.


What you do really is create a `DataList`, add the items from our slice of todos and use another widget api to render the list.

It will look quite similar to what we did here, but in our case, since the type of DataList we create will be of `Untyped` type, we will have to add one step more than a primitive type, to cast the data item to our own `models.Todo` struct.

here it is how you create the data list
```go
	data := []models.Todo{
		models.NewTodo("Some stuff"),
		models.NewTodo("Some more stuff"),
		models.NewTodo("Some other things"),
	}
	todos := binding.NewUntypedList()
	for _, t := range data {
		todos.Append(t)
	}

```
it would be nice if you could set the items on creation maybe but the API does not allow to do that yet.

then this is how the list creation looks like now
```go
			widget.NewListWithData(
				// the binding.List type
				todos,
				// func that returns the component structure of the List Item
				// exactly the same as the Simple List
				func() fyne.CanvasObject {
					return container.NewBorder(
						nil, nil, nil,
						// left of the border
						widget.NewCheck("", func(b bool) {}),
						// takes the rest of the space
						widget.NewLabel(""),
					)
				},
				// func that is called for each item in the list and allows
				// but this time we get the actual DataItem we need to cast
				func(di binding.DataItem, o fyne.CanvasObject) {
					ctr, _ := o.(*fyne.Container)
					// ideally we should check `ok` for each one of those casting
					// but we know that they are those types for sure
					l := ctr.Objects[0].(*widget.Label)
					c := ctr.Objects[1].(*widget.Check)
					diu, _ := di.(binding.Untyped).Get()
					todo := diu.(models.Todo)

					l.SetText(todo.Description)
					c.SetChecked(todo.Done)
				}),

```

this other casting bit is the one I found hard to get right too
```go
					diu, _ := di.(binding.Untyped).Get()
					todo := diu.(models.Todo)
```
We get a DataItem which is a `binding.Untyped` underneath, we need to `Get()` it, the cast it to our model, then we can finally use it.

I usually move the functions within a list to separate functions and make a small method on the models package to handle that type of casting, so it looks a bit less cluttered.

something like this
```go
// in models
func NewTodoFromDataItem(item binding.DataItem) Todo {
	v, _ := item.(binding.Untyped).Get()
	return v.(Todo)
}

// so in the list function will look like so
				func(di binding.DataItem, o fyne.CanvasObject) {
					ctr, _ := o.(*fyne.Container)
					// ideally we should check `ok` for each one of those casting
					// but we know that they are those types for sure
					l := ctr.Objects[0].(*widget.Label)
					c := ctr.Objects[1].(*widget.Check)
					/*
						diu, _ := di.(binding.Untyped).Get()
						todo := diu.(models.Todo)
					*/
					todo := models.NewTodoFromDataItem(di)
					l.SetText(todo.Description)
					c.SetChecked(todo.Done)
				}),
```

Anyway, now let's do the last step, how to add a new todo?
Just use the data list on the `addBtn` `func` like so

```go
addBtn := widget.NewButton("Add", func() {
		todos.Append(models.NewTodo(newtodoDescTxt.Text))
		newtodoDescTxt.Text = ""
	})
```

and once you click on it, it will magically add it to the list, and show a new list item on the List component.
As a small nice feature, we need also to clear the text entry so we are ready to add another one.

We could also use the `Prepend` method instead of `Append` so the Todo will take the first place in the list instead of the last.

## Other notes
- If you want to change the actual items it is better to create a slice of `*models.Todo` so they will use the real value of those rather than a clone.

- There is no `Remove` api in the DataList for now, so to remove something you need to hack around with the slice within.
```go
// to remove them all for example you should do something like this
        list, _ := todos.Get()
	list = list[:0]
	t.Set(list)
```


If you are interested in the code example I uploaded it on my github here: [github.com/vikkio88/fyne-tutorials/tree/main/todoapp](https://github.com/vikkio88/fyne-tutorials/tree/main/todoapp)

If you want to see a more complex example with some syntax sugar and sexy db persistence layer using [clover](https://github.com/ostafen/clover) you can look at this [gtodos](https://github.com/vikkio88/gtodos).

I also added an example branch without the db just for "fun" [here](https://github.com/vikkio88/gtodos/tree/dbless-example)

And finally, if you are interested in a more complex app example I posted [here](https://dev.to/vikkio88/fyne-ill-do-it-myself-adventures-in-desktop-app-development-2di1) about me rewriting a Password Manager in Fyne and managing in a couple of days to create and distribute the app using github actions.
[Muscurd-ig](https://github.com/vikkio88/muscurd-ig).


That is all folks, please let me know what you think or if something is not clear.

See you next time.