The AFib Rendering Model
Each AFib-aware UI element - screens, drawers, dialogs, bottom sheets and widgets - contains a function which takes a route parameter and a state view and returns a Widget. The Widget is a description of the user interface for that element.
The rendering function
If you look in a screen file, you will see a method like this:
The SPI object that is passed to your buildWithSPI
method contains everything you need to convert your application state -- data in your route parameter and state view -- into widgets that appear on the screen. Anytime your route parameter or state view change, AFib calls this function to re-render your UI element.
The SPI -- screen programming interface -- is a custom API you design which exposes both a simplified view of your data for rendering, and event handlers to handle user actions. The SPI's purpose is both to separate filtering and business logic from your UI code, and to make it easier to validate and drive your application in state tests, which are discussed below.
The route parameter and state view
The route parameter and state view are not immediately evident in this code, but they are present in the context member variable of the SPI. You could re-write the method above like this to see them better:
Widget buildWithSPI(YourScreenSPI spi) {
return buildWithContext(spi.context);
}
Widget buildWithContext(AFBuildContext<YourRouteParam, YourStateView> context) {
final routeParam = context.p; // or .routeParam if you prefer
final stateView = context.s; // or .stateView if you prefer
return ...;
}
In this example, you can see the route parameter and state view are part of the AFBuildContext. You can access the data within them to build your user interface.
Don't access the spi.context directly from UI code
Note that although you can reach down into the spi.context
directly from your UI code, doing so will make your app more difficult to test. For example, this code to increment a counter in response to a button press cannot be tested unless you build the UI.
Widget buildWithContext(YourScreenSPI spi) {
// generally don't reach down like this.
final context = spi.context;
return FlatButton(
child: Text("Increment Count from ${context.p.count}"),
onPressed: () {
// opps, this code can't be tested without building the UI.
final revised = context.p.reviseIncrementCount();
context.updateRouteParam(revised);
}
);
}
However, this code, which moves both the data access and the manipulation and update of the route parameter within the SPI will be testable without building the UI.
This rendering model is universal in AFib
This rendering model applies to screens, dialogs, bottom sheets, drawers, and AFib-aware widgets. In each case, AFib looks up the appropriate route parameter and state view for the UI element in question, and calls your rendering function any time either of them change.
All you need to do is write the code that converts the data in your route parameter and state view into a meaningful user interface.