Skip to content

App storage

Every app with an ID gets a private storage area. App.Storage returns a bellui.Storage for it. Use it for files the user does not pick themselves: drafts, caches, saved state.

store := a.Storage()
root := widget.NewLabel("Storage root: " + store.RootURI().String())

Documents

The Storage methods work on named documents in a Documents folder inside the storage area. The folder is created the first time you call Create.

MethodWhat it does
Create(name)Opens a new document for writing. Fails with storage.ErrAlreadyExists if it exists.
Save(name)Opens an existing document for writing. Fails with storage.ErrNotExists if it does not exist.
Open(name)Opens a document for reading.
Remove(name)Deletes a document.
List()Returns the names of all documents.
RootURI()Returns the URI of the whole storage area.

Writing is finished only when you close the writer. A save that should work whether or not the document exists tries Create first:

// saveNote creates the document the first time and overwrites it after that.
func saveNote(store bellui.Storage, name, text string) error {
	wr, err := store.Create(name)
	if errors.Is(err, storage.ErrAlreadyExists) {
		wr, err = store.Save(name)
	}
	if err != nil {
		return err
	}
	if _, err := wr.Write([]byte(text)); err != nil {
		wr.Close()
		return err
	}
	return wr.Close()
}

Reading is the same as for any other URI:

func loadNote(store bellui.Storage, name string) (string, error) {
	r, err := store.Open(name)
	if err != nil {
		return "", err
	}
	defer r.Close()
	data, err := io.ReadAll(r)
	return string(data), err
}

RootURI is an ordinary URI, so you can also use the functions in Files and URIs on it, for example to make your own subfolders next to Documents.

Where the storage area is

PlatformLocation
macOS~/Library/Preferences/bellui/<app ID>
Windows%USERPROFILE%\AppData\Roaming\bellui\<app ID>
Linux and BSDbellui/<app ID> inside the user configuration folder, usually ~/.config
Androidbellui inside the app’s private files folder
iOSbellui inside the app’s Documents folder
Webidbfile:///bellui/, kept in the browser’s IndexedDB

On Android and iOS the operating system already gives every app its own sandbox, so the app ID is not part of the path. The same folder also holds the app’s preferences file.

Without an app ID there is no storage area. RootURI logs an error and returns the system temporary folder, and the document methods return an error asking you to use app.NewWithID.

The complete example

// Documents in the app's private storage area.
package main

import (
	"errors"
	"io"
	"strings"
	"time"

	"github.com/belluikit/bellui"
	"github.com/belluikit/bellui/app"
	"github.com/belluikit/bellui/container"
	"github.com/belluikit/bellui/storage"
	"github.com/belluikit/bellui/widget"
)

func main() {
	a := app.NewWithID("com.example.appstorage")
	w := a.NewWindow("App storage")

	store := a.Storage()
	root := widget.NewLabel("Storage root: " + store.RootURI().String())

	files := widget.NewLabel("")
	refresh := func() {
		names := store.List() // the documents saved by this app
		files.SetText("Documents: " + strings.Join(names, ", "))
	}

	save := widget.NewButton("Save note", func() {
		if err := saveNote(store, "note.txt", "Saved at "+time.Now().Format(time.Kitchen)); err != nil {
			files.SetText("error: " + err.Error())
			return
		}
		refresh()
	})
	load := widget.NewButton("Load note", func() {
		text, err := loadNote(store, "note.txt")
		if err != nil {
			files.SetText("error: " + err.Error())
			return
		}
		files.SetText(text)
	})
	remove := widget.NewButton("Delete note", func() {
		_ = store.Remove("note.txt")
		refresh()
	})

	refresh()
	w.SetContent(container.NewVBox(root, save, load, remove, files))
	bellui.ShowAndRun(w)
}

// saveNote creates the document the first time and overwrites it after that.
func saveNote(store bellui.Storage, name, text string) error {
	wr, err := store.Create(name)
	if errors.Is(err, storage.ErrAlreadyExists) {
		wr, err = store.Save(name)
	}
	if err != nil {
		return err
	}
	if _, err := wr.Write([]byte(text)); err != nil {
		wr.Close()
		return err
	}
	return wr.Close()
}


func loadNote(store bellui.Storage, name string) (string, error) {
	r, err := store.Open(name)
	if err != nil {
		return "", err
	}
	defer r.Close()
	data, err := io.ReadAll(r)
	return string(data), err
}