Skip to content

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.