Almost a year ago, I wrote about I was really trying hard to get into Desktop GUI apps development here, and someone suggested me to try Fyne.
Which I did, and wrote some first impressions about it in here.
I said there that the docs weren't really clear and some stuff felt a bit hacky to do, which it is totally still the case, but since then many things have changed.
I tested Avalonia and loved it.
I even wrote an article here on how I wrote in just under 2 months a multiplatform password manager application, which builds on github action and works on Mac/Win and Linux.
Muscurd-i is the app, and the codebase is quite huge to be such a simple application.
Few weeks ago instead I got my coding mojo back and hacked a bit around with rust, vlang and went back to study go once again (I wrote about it in here also).
This is the way I learn programming languages, I do side projects and since then I felt like I had to give fyne another go.
I finally completed the todo application which I was mentioning on the post before, and I was quite silly not to see why it didn't work before codebase is here.
Now it all works like a charm, and since I was on a roll I decided to give a try to move my password manager to Fyne and golang.
I did.
It took me only 2 days to reach feature parity and a couple of hours to setup a set of github action to build the binaries.
This is the repo.
This is what the latest release page looks like here.
The only thing I needed in here which was not supported out of the box with Fyne, was a routing system, which I was shocked to find so easy to create myself using their binding apis and a bit of creativity.
In this project basically the app creates a context to pass around the views, and maps the string representation of the route to a view which it is "mounted" in the main window content:
views: map[c.AppRoute]func() *fyne.Container{
c.Setup: func() *fyne.Container { return ui.GetSetupView(&ctx) },
c.Login: func() *fyne.Container { return ui.GetLoginView(&ctx) },
c.List: func() *fyne.Container { return ui.GetPasswordListView(&ctx) },
c.Details: func() *fyne.Container { return ui.GetPasswordDetailsView(&ctx) },
c.AddUpdate: func() *fyne.Container { return ui.GetPasswordAddUpdateView(&ctx) },
c.About: func() *fyne.Container { return ui.GetAboutView(&ctx) },
},
}
// Getting the view given a route
func (a *App) getView() *fyne.Container {
key := a.ctx.CurrentRoute()
if content, ok := a.views[key]; ok {
return content()
}
return a.views[c.Login]()
}
// reacting to the view change
a.ctx.OnRouteChange(func() {
val := a.ctx.CurrentRoute()
a.log(fmt.Sprintf("route state changed %s", val))
if val == c.Quit {
a.application.Quit()
}
a.setView()
})
// changing a route from any view then looks like this
aboutPageBtn := widget.NewButtonWithIcon("", theme.InfoIcon(), func() {
ctx.NavigateTo(c.About)
})
I was really proud of that, I even did a small change so I could pass a parameter around, for routes with ids for example.
I know it feels a bit web app
like but every other ui framework has routing systems, that is just the way the UX world goes I guess.
I also managed, as mentioned before, to setup a whole set of github actions to build the app binary for Win, Linux and Mac, and it took me literally 2 hours without almost no trial and error that usually you end up doing when you work with CI/CD tools.
The app looks like this on linux after you install it:
Everything just worked and I felt productive like I probably never have on Desktop GUI app development.
What is next? Maybe I will finally finish one of the projects I tried to do many times before, a Football Manager clone focused on the Players Transfer Market.
Let's see if this mojo streak lasts.
And what about you? Have you tried fyne? GO AND DO IT NOW!