Time in AFib
Time functionality is more likely to contain bugs
AFib is alpha software, and the time testing functionality has a number of areas where there may be bugs. In particular, if you have adjusted the current time within an AFib test or prototype, and are not getting the expected result in your code, I would suspect a bug.
Why Should AFib Have a Notion of Time?
Time is a fundamental physical reality that all applications must contend with. AFib enables you to manipulate the current time that your app sees via the prototype drawer. This makes it easy to test functionality in your app that depends on the passage of time.
For example, in a state test running in the UI, you might add a todo item today (Monday), then move time forward 24 hours and add another (on Tuesday), and then test functionality which filters for todo items created in the last 24 hours.
In order for this ability to manipulate time to work well, all the third-party AFib-aware libraries need to refer to AFib's notion of time. As a result, time is a part of AFib.
AFib's Model of Time
Local vs UTC Time
AFib starts with Dart's model of time, which defaults to your local time zone, and allows you to convert to UTC.
Push Time vs Pull Time
In AFib, we need to distinguish between two different uses of time.
When you use the current time to render your UI (for example, to display a digital clock on screen), then you are using 'push time', a time that is 'pushed' into your state, causing your UI to update just like any other state change.
When you use the current time as part of a response to an event (for example, a user clicks a button to create a Todo item, and you want to save its creation time), then you are using 'pull time'. You can always pull the exact current time in response to any event.
Absolute Time in AFib
You are probably familiar with the Unix 'seconds since 1970' convention. AFib allows you to declare your own 'epoch date' (perhaps 1 year before you started development, since most apps won't manage data that predates their own creation). It then allows you to access not just the absolutes seconds since your epoch date, but absolute years, months, days, minutes, and so on since your epoch.
I have often found that storing time only to the required level of specificity (e.g. a meal plan occurs on a specific day, and thus can use the 'absoluteDay' since the epoch to know when the day the meal plan refers to), makes it much easier to understand, debug, and perform downstream analytics on time-based data. This notion of 'absolute time to a particular specificity' grew out of work with time-based star schemas in SQL, which are conceptually similar and make time-based queries much simpler.
Of course, if you don't like this form of absolute time, you can simply ignore it.
Configuring Time in AFib
Configuring Absolute Time
Your absolute time will always start at the beginning of January 1st on your choosen epoch year. You can set the epoch year in your xxx_config.g.dart
file, look for the line:
If your base year is 2019, and the current year is 2023, then the absoluteYear
is 4, and so on. Obviously, once your app is in production it would be unwise to change this value.
Configuring Push Time
All AFib project styles automatically add the following line to your startup query. This line configures how push-time behaves in AFib. The updateFrequency
specifies the maximum frequency with which time updates will be pushed into your AFib state. Your app cannot update its UI at a rate higher than that rate.
The updateSpecificity
defines the default rate at which UIs will update. This rate must be less than or equal to the updateFrequency
in order to make sense.
In the example below, some UI elements of your app could recieve updates as frequently as every 10 seconds. However, by default, UI elements will only update when the day value in the date changes.
context.executeStandardAFibStartup(
updateFrequency: const Duration(seconds: 10),
updateSpecificity: AFTimeStateUpdateSpecificity.day,
);
Changing updateSpecificity
for a particular state view
The section on state views above describes how to create additional state views. One of the thing you can configure in a custom state view is its update specificity. You can do so by adding a line like:
If you combine this example with the example above, then UI elements which use the custom state view containing this line will update any time the minute value changes, while elements using the default state view will update only when the day changes.
Using Time in AFib
AFib times are contained in an AFTimeState object.
Using Push Time
If you need to use a time value to render your UI, you can access it using context.accessPushTime()
in your SPI. The value returned by this call is only accurate to the time-specificity for your state view.
By default, new AFib projects use a time specificity of "day" to avoid confusing new developers with extra breakpoint hits they might not be expecting. However, you can easily increase the specificity by opening your xxx_default_state_view.dart
file and searching for 'specificity'. You will see that the state view is populated with a time of a particular specificity.
Using AFUIDebugTimeStateWidget to understand Push Time
If you add lines like this to an AFib UI element, your UI will display a card containing an explanation of the current push time state.
final currentTime = spi.context.accessPushTime();
rows.add(AFUIDebugTimeStateWidget(displayTime: currentTime));
If you then play with the time specificity in your state view, and the update frequency specified in your startup query (note that you need to fresh the app if you change the startup query), it should be very helpful in clarifying how those values impact how often your UI gets rebuilt in response to time changes.
Using Pull Time
When a user interacts with your UI, the event occurs at a specific point in time, and you do not need to worry about the update frequency of the state view. In that case, you can use context.accessOnEventContext()
to access the event context, and then eventContext.accessPullTime()
to access the current time.
Converting Between Local and UTC Time
By default, times in AFib are in the local time zone. You can convert to and from UTC using AFTimeState.reviseToUTC()
and AFTimeState.reviseToLocal
. Like all other revise methods, these methods return a new AFTimeState
object, since AFTimeState
is immutable.
You can use AFTimeState.isUTC
to determine if a time is UTC or not.
Manipulating Time
You can manipulate time in prototype mode.
Manipulating Time in Prototype Mode
To manipulate time in a UI prototype, swipe in the Prototype Mode drawer from the right, and select the time tab. Then, enter a duration (e.g. "2d" or "3h 10m") and click the Add or Subtract button. The displayed time will update both in the prototype mode drawer, and in any UI which is displayed.
Manipulating Time in State Tests
You can modify time in a state test using AFStateTestDefinitionContext.executeAdvanceTime
which adjusts the current time using a duration, or AFStateTestDefinitionContext.executeSetAbsoluteTime
, which sets the current time to some absolute value.