Skip to content

Use Prototype Mode

AFib provides a prototype mode, which enables you to view and and interact with your UI in three different prototype forms - UI Prototypes, Wireframes, and State Tests

I do most of my AFib development in prototype mode.

Entering prototype mode

To enter prototype mode, open the file lib/initialization/xxx_config.g.dart. This file contains a variety of configuration settings. The very first one is the environment value. Change this value from AFEnvironment.debug to AFEnvironment.prototype.

lib/initialization/xxx_config.g.dart
void configureAfib(AFConfig config) {
...
  config.setValue("environment", AFEnvironment.prototype);
...
}

If the debugger is already running, you can apply this change by hitting the refresh icon (green circle below). If it is not, you can simply start the app.

Debug Toolbar

Debug Toolbar (green circle for refresh)

When your app starts, it will not display your standard startup screen. Instead, it will display a screen for navigating among various prototypes and tests that exist for your app.

Prototype mode home screen

Prototype mode home screen

Starting Directly into a Prototype

When you are focused on working with a single UI prototype, state test, or wireframe, you can configure AFib to startup directly into it. To do so, change the environment to one of the appropriate startupIn... choices:

lib/initialization/xxx_config.g.dart
void configureAfib(AFConfig config) {
...
  config.setValue("environment", AFEnvironment.startupInScreenPrototype);
  // or
  config.setValue("environment", AFEnvironment.startupInStateTest);
  // or
  config.setValue("environment", AFEnvironment.startupInWireframe);
...
}
Then, open prototype.dart, and use typeahead to set the desired UI prototype, state test, or wireframe you want to start into:

void configurePrototype(AFConfig config) {   
     // 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);

     // use this to configure your favorite tests on the prototype home screen
     config.setFavoriteTests([
       // EVEXStateTestID.yourTestId,
     ]);

What Does Prototype Mode Provide?

The following sections cover the four main sections prototype mode provides -- UI prototypes, wireframes, state tests, and libraries.

UI Prototypes

In AFib, all UI is derived from data in a route parameter and a state view. A UI prototype is a test declaration that specifies a route parameter and a state view to use in populating a test instance of a UI component.

AFib renders the UI using the test data provided. You can use prototype mode to view and manipulate the prototype.

A Sample UI Prototype declaration
void _defineStartupScreenPrototypeInitial(AFUIPrototypeDefinitionContext context) {  
  var prototype = context.defineScreenPrototype(
    id: TDLEPrototypeID.startupScreenInitial,
    stateView: TDLETestDataID.tdleStateFullLogin,
    navigate: StartupScreen.navigatePush(clickCount: 3),
  );
  ...
}

As you can see from the code above, a UI prototype is nothing more than a unique identifier, a state view, and a route parameter (the navigate portion) which are used to hydrate the screen.

  • Although you can explicitly provide a state view, you will more often provide an identifier for test data declared in your lib/test/test_data.dart file, which is discussed more below. AFib automatically creates a 'full login' state view, which is meant to contain a single valid state with data for a fairly well-developed signed in scenario. You can declare additional states, and reference them across many different prototypes (and other tests).

  • Each screen (and drawer, dialog, and bottom sheet), has a static navigatePush method which is used to navigate to that UI. It will often take parameters that allow the caller to specify the initial state of the screen (in this case, the initial clickCount). The AFNavigatePush action which is returned contains the initial route parameter for the screen. The route parameter also contains a screen identifier which indicates which screen should be visited.

When you generate a new UI element, AFib automatically generates a default UI prototype, but you can easily add additional prototypes with different test data by copying and pasting the initial one and modifying it.

In order to navigate to a UI prototype, tap on UI Prototypes at the top of the prototype mode home screen. Note that the UI prototypes screen has tabs for each of AFibs primary UI types - screens, drawers, dialogs, bottom sheets and widgets -- at the top.

Prototype Mode

UI Prototypes

As you generate additional UI elements using the bin/xxx_afib.dart generate command, a prototype for each one will automatically appear here.

You can navigate into the prototype by tapping its name.

Using the prototype drawer

In prototype mode, AFib automatically installs a prototype drawer on the right hand side of the screen.

Prototype mode drawer

Prototype mode drawer

You can open it by swiping in from the right. You can use the drawer to:

Exit the Prototype

Your most frequent use for the prototype drawer will be to exit the prototype using the Exit button near the top of the drawer. If your screen has the standard back button, that will also work.

Manipulate Time

If you choose to use AFib's standardized time system, the drawer also allows you to manipulate time, moving it forward and backward, and viewing the current value.

View and Manipulate Theme Values

You can view the theme values (usually custom third party theme values) which contribute to the page's appearance. You can also manipulate some theme values, for example switching into and out of dark mode easily.

Run Tests and View Results

You can execute UI tests and view their results on two separate tabs. Note that UI tests are primarily run from the command line, but visual execution can be useful for test development and debugging.

What are UI Prototypes Good For?

Prototyping

First and foremost, UI prototypes are good for prototyping. You can use them to get something on the screen quickly, so that you can brainstorm about how your app should work and how it should present functionality to the user.

You don't need to worry about navigation or state management in a UI prototype.

Although you can hard-code data directly into your prototype's UI code, AFib makes it easy to prototype your app's data model at the same time you are prototyping its UI. I like this approach, both because it means the code is ready to be populated with real data later, and because thinking about the data helps me develop a more realistic and well-thought-out prototype.

UI Tests

Each prototype can have one or more UI tests associated with it. These tests drive the UI, and validate that it produces the expected result, starting from the initial state specified for the UI prototype.

Continued from above, a test definition for a UI Prototype
  ...
  prototype.defineSmokeTest( 
    body: (e) async {
      await e.matchText(HCWidgetID.textCountRouteParam, ft.equals("0"));
      await e.applyTap(HCWidgetID.buttonIncrementRouteParam);
      await e.applyTap(HCWidgetID.buttonIncrementRouteParam);
      await e.applyTap(HCWidgetID.buttonIncrementRouteParam);
      await e.matchText(HCWidgetID.textCountRouteParam, ft.equals("3"));
  });
}
These tests can be executed from the command line by running dart bin/xxx_afib.dart test.

You can also run these tests from the prototype drawer described above, and watch them execute.

I find the ability to see the initial test state of a prototype, and to watch it execute, extremely helpful in fixing bugs that are revealed in the command-line testing.

Both you (via the UI) and your test code can manipulate the route parameter and state, and the manipulations are applied as though the UI prototype was running in a real app.

The AFib APIs which allow for navigation between screens, and for executing queries to access and manipulate external state (described below) are disabled in UI prototypes. However, if your create a test which causes a navigation API to be called, or a query to be executed, the AFib test captures those events and provides an easy way for you to access them and validate their properties within the test.

Wireframes

Just below the UI prototypes, you can navigate down to the list of wireframes. Wireframes are useful for connecting multiple UI prototype screens (and dialogs, bottom sheets, etc), into a single mocked up app which appears to have live navigation and state moving between screens.

Wireframes are fundamentally throw away, but they can be used at the very beginning of your app's development to quickly produce a demonstration video, or to walk users through an app that appears functional in a planned way.

State Tests

Farther down the home screen, there is a section for state tests. You can click on it to see all your defined state tests.

What is a state test?

A state test allows you to manipulate and validate your app just below the UI level (at the SPI, or Screen Programming Interface, level), but does not actually build the UI. It also provides fixed or dynamically generated responses to all your AFib queries - the building blocks AFib uses to allow access to and manipulation of external state (usually persistent state in sqllite, firebase, device APIs, etc).

Why does it show up in prototype mode?

Given that a state test does not build your UI, it may seem surprising that it appears in Prototype Mode. When a state test finishes execution, it produces a valid state for your app. Although AFib does not render your UI during the test, it can render this final UI, and dump you into it. That is what happens when you click on a state test in prototype mode -- the state test executes fully, then AFib builds the UI associated with its final state and dumps you into it.

What are state tests good for?

Testing

I allocate at least 85% of my testing effort to state tests. They allow for efficient validation of business state, and manipulation of your app at a near-UI level, without the tedium of working with rendered widgets (which often represent information in a form that is good for humans but poor for programatic validation).

Test Environments for Development or Testing

Because your state test controls what data is returned from your queries (your access to persistent external state), you can use state tests to build test environments with data appropriate for different test scenarios (e.g. unregistered user, registered user not logged in, registered user logged in, new user with no data, users with existing data that is useful for various test scenarios, etc.)

When prototype mode dumps you into the UI associated with a test's final state, the stubs for AFib queries that handle external state access continue to function. As a result, your state test can provide a robust initial test state, and then allow you to continue playing with that test state interatively through the UI.

Debugging

AFib also supports an executeDebugStopHere() call in state tests. When you call that function in a test, it stops the test at that point, then renders the UI and dumps you into it. This is useful for debugging failures that happen in the middle of state tests, which are often easily understood once you see the UI representation of the current state, and realize that it is not quite what you were expecting.

Libraries

The libraries section allows you to prototype modifications to UI imported from third-party AFib-aware UI libraries like AFib Signin.

The libraries section allows you to navigate into the prototype mode of each AFib-aware UI library you have integrated. If you override the theme that library, your theme modifications will show up in real-time in these UI prototypes.

Prototype Mode Conveniences

Starting Directly Into Prototypes

As described at the beginning of this section, you can startup directly into a UI prototype, state test, or wireframe you are focused on.

Favorite Tests

You may have a few tests (most likely state tests) which are very useful for you in your everyday development. For example, in the main app that I develop with AFib, I have a state test which dumps me into a logged in version of the app with a nice full set of test data.

You can specify your own favorites by opening the file:

lib/initialization/environments/prototype.dart

and adding test IDs to the config.setFavoriteTests array.

  config.setFavoriteTests([
    // XXXStateTestID.yourTestId,
  ]);

You will find the test ids in the file lib/xxx_id.dart. You can also use auto-complete starting from XXXStateTestID... or XXXPrototypeID... for UI prototypes.

Recent Tests

Whenever you execute a test from the command line, like this:

dart bin/evex_afib.dart test evex_pr_counterManagementScreenInitial

     overwrite lib/initialization/evex_config.g.dart
       success renamed 0 files, created 1 files, modified 0 files, created 0 folders

------------------------------------------------------------
Running at size phone-standard:portrait / 1170.0 w x 2532.0 h
------------------------------------------------------------
Running in locale en_us
------------------------------------------------------------
Afib Single-Screen Tests:
------------------------------------------------------------
evex_pr_counterManagementScreenInitial                                   
  smoke.....................................................    3 passed 
                                                       TOTAL    3 passed 
------------------------------------------------------------
                                                 GRAND TOTAL    3 passed (in 1.62s)
------------------------------------------------------------
00:03 +1: AFib Test Afib Test                                                                                                                                            00:03 +1: All tests passed!                             

It will automatically be added to the recent tests list in prototype mode. That list is just below your favorite tests. When I wish to work on a test, I often run it by itself from the command line, then refresh the UI in the debugger so that I can easily access it from the top of the recent tests list.

Note that the recent test list is in your xxx_config.g.dart file. You can edit it their directly if you want:

  config.setValue("tests-recent", ["evex_pr_counterManagementScreenInitial"]);

There is a search box at the bottom of the prototype mode screen. You can use one or more space-separated search strings to select and show specific tests quickly, without having to navigate through the lists of tests that prototype mode provides.