Preferences
Preferences are small values your app wants to remember between runs: a user’s name, the last tab they opened, whether they turned on dark mode. You set a value and Bellui saves it for you.
Getting the preferences
// Preferences are stored per app, so the app needs a unique ID.
a := app.NewWithID("com.example.preferences")
prefs := a.Preferences()App.Preferences
returns the app’s
bellui.Preferences.
Saving to disk needs an app ID, so create the app with app.NewWithID.
Without an ID, values still work while the app runs but are not saved.
Reading and writing values
You read and write with generic functions in the bellui package. The type
parameter is the type you want back.
launches := bellui.PrefOr(prefs, "launches", 0) + 1
bellui.SetPref(prefs, "launches", launches)
name, ok := bellui.Pref[string](prefs, "name")
if !ok {
name = "stranger"
}| Function | What it does |
|---|---|
Pref[T](p, key) | Returns the value and true, or the zero value and false if the key is missing or holds another type. |
PrefOr(p, key, fallback) | Returns the value, or fallback if it is missing. The type is taken from fallback. |
SetPref(p, key, value) | Stores the value. |
p.RemoveValue(key) | Deletes the key. |
The types you can store are bool, int, float64 and string, and slices
of those. The compiler rejects anything else. Numbers are converted for you
where needed: an int saved earlier can be read back as an int even though
the stored form may not keep the difference between whole numbers and
decimals.
Writing from a widget’s callback is the usual pattern:
entry := widget.NewEntry()
entry.SetText(name)
entry.OnChanged = func(s string) {
bellui.SetPref(prefs, "name", s)
}
dark := widget.NewCheck("Dark mode", func(on bool) {
bellui.SetPref(prefs, "dark", on)
})
dark.SetChecked(bellui.PrefOr(prefs, "dark", false))Lists
PrefList,
PrefListOr and
SetPrefList
do the same for slices. The type parameter is the element type.
recent := bellui.PrefListOr(prefs, "recent", []string{})
recent = append([]string{"opened at launch " + strconv.Itoa(launches)}, recent...)
if len(recent) > 3 {
recent = recent[:3]
}
bellui.SetPrefList(prefs, "recent", recent)Reacting to changes
AddChangeListener registers a function that runs after any preference
changes. It does not tell you which key changed, so read the values you care
about.
changes := widget.NewLabel("")
prefs.AddChangeListener(func() {
// Runs on the goroutine that made the change: the UI thread when your
// code sets a value, a background one when the file changes on disk.
bellui.RunOnMain(func() {
changes.SetText("Name is now " + bellui.PrefOr(prefs, "name", ""))
})
})On the desktop, Bellui watches the preferences file and reloads it when
another process changes it. Listeners then run on that watcher’s goroutine,
not the UI thread. bellui.RunOnMain runs your UI update inline when you are
already on the UI thread, and moves it there otherwise.
Where preferences are stored
On desktop and mobile platforms the values are written to a file named
preferences.pxf in the app’s configuration folder (the same folder as
app storage). The file is written when a value changes, with
rapid bursts of changes batched together, and once more when the app exits. On
the web the values are kept in the browser’s localStorage.
The complete example
// Typed app preferences that persist between runs.
package main
import (
"strconv"
"github.com/belluikit/bellui"
"github.com/belluikit/bellui/app"
"github.com/belluikit/bellui/container"
"github.com/belluikit/bellui/widget"
)
func main() {
// Preferences are stored per app, so the app needs a unique ID.
a := app.NewWithID("com.example.preferences")
prefs := a.Preferences()
w := a.NewWindow("Preferences")
launches := bellui.PrefOr(prefs, "launches", 0) + 1
bellui.SetPref(prefs, "launches", launches)
name, ok := bellui.Pref[string](prefs, "name")
if !ok {
name = "stranger"
}
greeting := widget.NewLabel("Hello " + name + ", launch number " + strconv.Itoa(launches))
entry := widget.NewEntry()
entry.SetText(name)
entry.OnChanged = func(s string) {
bellui.SetPref(prefs, "name", s)
}
dark := widget.NewCheck("Dark mode", func(on bool) {
bellui.SetPref(prefs, "dark", on)
})
dark.SetChecked(bellui.PrefOr(prefs, "dark", false))
recent := bellui.PrefListOr(prefs, "recent", []string{})
recent = append([]string{"opened at launch " + strconv.Itoa(launches)}, recent...)
if len(recent) > 3 {
recent = recent[:3]
}
bellui.SetPrefList(prefs, "recent", recent)
changes := widget.NewLabel("")
prefs.AddChangeListener(func() {
// Runs on the goroutine that made the change: the UI thread when your
// code sets a value, a background one when the file changes on disk.
bellui.RunOnMain(func() {
changes.SetText("Name is now " + bellui.PrefOr(prefs, "name", ""))
})
})
reset := widget.NewButton("Forget my name", func() {
prefs.RemoveValue("name")
})
w.SetContent(container.NewVBox(greeting, entry, dark, reset, changes,
widget.NewLabel("Recent: "+joinLines(recent))))
bellui.ShowAndRun(w)
}
func joinLines(items []string) string {
s := ""
for _, it := range items {
s += "\n " + it
}
return s
}