Writing AFib Libraries
In order to write an AFib library effectively, you should understand how to use one. Consequently, if you have not already read the prior section on using AFib libraries, please do so.
Creating a Library
As with an app, you will pre-create either a dart package (for a state library) or a Flutter app (for a ui_library). Then, you will convert the package or app into either a ui_library or state_library using afib_bootstrap.
To create a library, see the help for afib_bootstrap create
, which explains how to create either a ui_library or state_library (as opposed to an app).
As with the app, you will subsequently use the commands in bin/xxx_afib.dart
to generate code in your library.
Best Practices in AFib-aware Libraries
This section describes conventions open source libraries can use to achieve both flexibility and maintainability.
Library Programming Interfaces (LPIs)
Create a ...ManipulateStateLPI
If your library has its own state, create a ...ManipulateStateLPI
which provides high-level methods for manipulating the state.
Expose LPIs that are meant to be overriden
If your library needs its host application to provide some functionality (e.g. the actions to be taken when the user clicks a specific button), expose that as part of an LPI.
Regarding persistant data, it might be tempting to write a abstract query classes which only provide a finishAsyncWithResponse
method that updates your library's internal state, but requires the app to provide a startAsync
method specific to its backend. In practice, I think it is very difficult to anticipate how the controlling app will want to store its data. Instead, I recommend creating an LPI with simple serialization method (e.g. writeUser
and readUsers
) and allowing the controlling app to handle all serialization logic. That app can then use your ...ManipulateStateLPI
to record the results into your state.
Consider code-generation as an alterantive to LPIs
In some cases, it may make more sense to provide custom code generation rather than attempting to collaborate with the host app via an SPI. For example, afib_firebase_firestore
used code generation to generate queries that read and write a Firestore database.
Functional Themes
Push UI Elements into Conceptual Theme Methods
As the example in the 'Using AFib Libraries' suggests, if you hard code an icon directly into your UI code, then the controlling app will not be able to alter it. However, if you place that icon's creation within a theme method then the controlling app can override your theme/method and return its own icon.
Prefer using theme.child...
, and pass in Widget ids
Anytime you use a theme.child...
variant to create a widget, you are giving the controlling app a hook to modify your user interface. Consequently, you should prefer these methods to hard-coding UI directly into your library (e.g. prefer t.childText(...)
to Text(...)
).
Anytime you use a theme.child...
variant, specify a widget id to its wid:
parameter. Doing so makes it easier for the controlling app to identify and modify specific aspects of your user interface.
Always return Widgets, not more specific types
Your theme methods should always return widgets, not more specific types. This allows the controlling app to return significantly enhanced UIs from theme methods.