-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Data API
Note
This document is archived as it refers to a design process. The live documentation is available at https://docs.fyne.io/explore/binding
The objective of this page is to define a Data Binding API that could be used by all widgets that need to handle data like Select, Radio, Checkbox, List and Table.
The List and Table widgets have not been designed / implemented yet, but some discussion have been done with regard to existing widgets and are summarized in the next sections.
Following recent conversations we have refined a little what the requirements for data binding might be. The main points agreed are as follows:
- The main aim is to make it simpler to bind data sources to widget content. We will need to handle primitive types, complex data and large data sets, without a complex API.
- The API should be consistent with our approach to a clean, clear API with lots of compile checking (including type safety)
- Support the design of direct field access and simple widget creation for the base cases with the ability to bind in addition to the main functionality
- When data changes occur we should execute the calculations in a reliable order and avoid race conditions
- Data types should be convertible (i.e. a float being displayed in a label (string) - with the possibility of developer controlled formatting
The DataItem
interface defines functions to add and remove listeners. These listeners provide the opportunity to hook in to be informed of change events so that widgets can update accordingly. Type specific implementations of this interface will provide access to the actual data.
The callback is of a defined type rather than an anonymous API so that it can be removed later without additional index parameters.
type DataItemListener interface {
DataChanged(DataItem)
}
type DataItem interface {
AddListener(DataItemListener)
RemoveListener(DataItemListener)
}
The DataMap
interface is like a DataItem
except that it has many items each with a name.
The change listener is called when an item (or multiple) within the map is changed.
type DataMapListener interface {
DataChanged(DataMap)
}
type DataMap interface {
AddListener(DataMapListener)
RemoveListener(DataMapListener)
Get(string) DataItem
}
The DataList
interface defines an interface that returns multiple DataItem
s. You can consider it like
[]DataItem
except that it can support lazy loading and advanced features like paging.
The change listener is notified if the number if items in the source changes - an addition or deletion - but not if items within it change.
type DataListListener interface {
DataChanged(DataList)
}
type DataList interface {
AddListener(DataListListener)
RemoveListener(DataListListener)
Length() int
Get(int) DataItem
}
Future extensions such as PagedDataList
may be added to bundle more complex data behaviours within the toolkit.
The bindings provide various implementations to handle standard types: bool
, float64
, int
, string
.
These types all follow the same pattern, we use string
to illustrate:
type String interface {
DataItem
Get() string
Set(string)
}
func NewString() String {
blank := ""
return &stringBind{&blank}
}
func BindString(s *string) String {
return &stringBind{val: s}
}
You will see that type bindings can be created using a new anonymous variable, or by binding to an existing variable using a pointer.
As a strictly typed API the data you wish to bind may not match the expected type of the widget. For these situations there will be conversion functions that can format or parse one type from another.
func (Int) String() String // convert int to string binding
func (Int) Format(string format) String // format into to a string binding
func (String) ToInt() Int
func (String) ParseInt(string format) Int
The returned types of DataItem will two-way bindings for the new type, so using ToInt
on &String{"5"}
would create a binding that can render ints. When the value of the String
changes it will be reflected in the derived Int
, but also changing the Int
value will reflect the change in a string representation.
The introduction of the DataItem
and DataList
interfaces will break the current API for the Radio
and Checkbox
widgets.
The break is related to the type changes for the Selected
, Options
and OnChanged
fields.
package widget
import "fmt"
type Select struct {
baseWidget
Selected binding.String
Options binding.StringList
OnChanged func(String)
hovered bool
popover *PopOver
}
func NewSelect(options binding.StringList, changed func(binding.DataItem)) *Select {
combo := &Select{baseWidget{}, nil, options, changed, false, nil}
options.AddListener(func(binding.StringList) {
combo.Refresh()
}())
Renderer(combo).Layout(combo.MinSize())
return combo
}