Learning AFib Efficiently
In order to be useful for prototyping, AFib must be reasonably easy to learn. In addition to the trailer video, the tutorial video, and this guide, the code itself is designed to be easy to learn.
Using YouTube Videos
If you somehow made it this far without viewing the tutorial video, you should start there. It will help you get started efficiently.
Using Project Styles
The starter project styles will help you get off to a quick start. In general, I doubt there is much educational value in starting with the app-starter-minimal
style if a more specific style is relevant to your project.
Code Generation
AFib provides the generation command:
This can can be used to generate code for all the key conceptual elements in AFib.Correctly setting up a new element in AFib sometimes requires modifying multiple files. This command does those modifications for you. I recommend you start by using it for all code generation. You may ultimately stop using it for elements which generate a single file.
You can use dart bin/xxx_afib.dart help generate
to see all the generation options. You can also specify a subcommand (e.g. help generate ui
or help generate query
) to see more detailed help for those subcommands.
Note that some third part components provide their own generate commands. For example, if you used the app-starter-signin-firebase
project style, your project is already using afib_firebase_firestore
package, which exposes an affs:generate
command.
You can use dart bin/xxx_afib.dart help
to see all available top-level commands.
Correlation between generate command and file locations
Note that the form of the generate command and the paths of the created files are related. The subcommand after generate is generally the folder in which the file is generated, while often the final suffix of the generated class name is used as a secondary folder. For example:
dart bin/xxx_afib.dart generate ui MyNewScreen
Will yield a file in:
lib/ui/screens/my_new_screen.dart
Similarly:
dart bin/xxx_afib.dart generate state MyModelRoot
Will yield a file in:
lib/state/root/my_model_root.dart
Using Contexts for Discovery
Contexts
Almost all Afib-related code will have access to a context
variable. It will either be passed
as a parameter to a function you are writing, or will be a member variable in a class you are
writing. The context
variable provides an API that let's you access all the AFib functionality
that is relevant in that context.
Context Documentation
Because the context is your starting point for understanding Afib, I have made a particular effort to document context classes in AFib's API documentation. I recommend you skim through that documentation to get a feel for all the available functionality in each of the different contexts.
Context Typeahead
Although many specialized AFib contexts (e.g. testing contexts) have their own bespoke APIs, the AFib contexts which you will use when writing production code share certain common typeahead prefixes.
Familiarizing yourself with these prefixes gives you a good sense of the core ways you will interact with AFib when writing production code.
- context.access...
- Used to access state.
- context.update...
- Used to update state.
- context.navigate...
- Used to navigate between screens
- context.show...
- Used to show user interface (e.g. drawers, dialogs, bottom sheets) that does not navigate away from the current screen.
- context.execute...
- Used to execute some kind of action (described below, usually a query) in AFib.
Debugging in VS Code
Running Your App in the Debugger
You can run your app in the debugger in VS Code by opening lib/main.dart
, and clicking the
debug
text just above the main method.
Pay attention to the simulator target in the lower right hand corner of VS Code:
You can change it by clicking on it.
VS Code Reverting to Another Simulator
Sometimes if you shut down the emulator, VS Code will automatically revert to something else like macos
as the environment. If you haven't been actively developing for macos
, you may get compiler errors due to out of date podfile dependencies that will confuse you if you think they are new and related to your ongoing development in the previous environment.
App Failing to Start in the Debugger
If you app fails to start within the debugger, try running flutter run
from the command line. This may indicate a compiler error in an included library, which VS Code does not show by default, but which flutter run
will.
Consider Starting with iOS
Although Flutter is a Google technology, there seem to be several minor hassles when working in the Android emulator -- issues with the device storage not being large enough, and the default Android version being too low. These are all easy to resolve, but you might consider starting on iOS.
Seeing the state
All the state associated with your app is visible in the debugger. Inspecting the state can sometimes help identify bugs. To find the state, inspect context.debugOnlyPublicState
or spi.debugOnlyPublicState
in the debugger.
For now, the children of the public state that you care about are:
- route
- Contains information about all the route parameters in the screen hierarchy and the global pool, including any child route parameters.
- components
- Contains global component state information, including your current XXXState object and any third-party state for your app.
Using Logging
I rarely use logging for debugging because the dart debugger in VS Code is excellent. However, it can occasionally be useful when working with asynchonous processes where stopping in the debugger confuses the order of events.
In that case, all AFib contexts have a log method:
The file lib/initialization/xxx_config.g.dart
specifies which logging areas are enabled:
// --logs-enabled
// [//] Disable all items after this in the logs-enabled array
// [af:config] Logging on any non-test definition/initialization context, and of afib.g.dart/startup configuration values
// [af:query] Internal AFib logging for queries
// [af:route] Internal AFib logging related to routes and navigation
// [af:state] Internal AFib logging related to app state
// [af:test] Internal AFib logging for testing
// [af:theme] Internal AFib logging related to theming
// [af:ui] Internal AFib logging for UI build
// [all] Turn on all logging
// [query] App/third-party logging on AFStartQueryContext.log, AFFinishQuerySuccessContext.log or AFFinishQueryErrorContext.log
// [standard] Logging for app/third-party query and ui, plus afib route and state
// [test] App/third-party logging on test contexts: AFUIPrototypeDefinitionContext.log, AFStateTestDefinitionContext.log, AFDefineTestDataContext.log, AFDefineTestDataContext.log, AFWireframeDefinitionContext.log
// [ui] App/third-party logging for any AFStateProgrammingInterface.log (e.g ...SPI.log) or AFBuildContext.log.
config.setValue("logs-enabled", ["query", "ui", "test", "//", "af:route", "af:state"]);
Note that the //
item disables everything that appears after it in the list, which can be useful for enabling/disabling logging without a lot of typing.
Debugging Your command-line tests
Although you run the command dart bin/xxx_afib.dart test
to launch your tests, you should not debug that command in order to debug your tests. It simply updates the xxx_config.g.dart
configuration file and executes lib/test/afib/main_afib_test.dart
.
Use typeahead to find the main test file
Note that in VS Code, you can find this file with the typeahead main test
in the file lookup (Command-p).
To debug your tests, just click the debug
text just above the main
function in that file.
Note that if you have executed a specific test on the command line, for example:
The test to run is stored in the xxx_config.g.dart
file, and it will be executed when you run debug in the main function.
Some Test Code Runs at Startup
Note that tests in AFib are often implemented as closures which are captured and the executed later by AFib. As a result, the code at the outermost test definition context often runs at startup, capturing the closures, while the code within the closures runs during the tests themselves. If you are not hitting a breakpoint when running a test, try putting the breakpoint deeper in the test code.
Debugging State Tests and UI Tests in Prototype Mode
Note that in additon to running tests from the command line, you can execute both state tests and UI Tests in Prototype mode. This allows you to see the user interface generated by the test, which can be extremely helpful for debugging, and much more efficient that trying to visualize the UI by inspecting variables in the debugger.
The sections on state testing and UI testing explain how to visually debug your tests in more detail.
Debugging Your xxx_afib.dart command
If you develop your own command-line extensions to the bin/xxx_afib.dart
command, you may
wish to debug it. You can do so by debugging the main function found within that file.
Focusing on a specific UI prototype, state test, or wireframe
You can start AFib directly into a specific UI prototype, state test, or wireframe by changing two files.
First, change the xxx_config.g.dart
to AFEnvironment.startupInScreenPrototype
, AFEnvironment.startupInStateTest
, or AFEnvironment.startupInWireframe
respectively.
Then, open prototype.dart
, and specify the ID of the UI prototype, state test, or wireframe you wish to startup into, as shown below.
// use this, plus AFEnvironment.wireframe to start up directly into a wireframe.
// config.setStartupWireframe(EVEXWireframeID.initial);
// use this, plus AFEnvironment.stateTest to startup directly into the terminal state of a state test.
// config.setStartupStateTest(EVEXStateTestID.yourStateTest);
// use this, plus AFEnvironment.screenPrototype to startup directly into a screen prototype.
// config.setStartupScreenPrototype(EVEXPrototypeID.searchScreenInitial);