Skip to content

Commit

Permalink
docs: add architecture and navigation topics for the AES
Browse files Browse the repository at this point in the history
  • Loading branch information
marwfair committed Aug 9, 2024
1 parent 390d7f5 commit 25502a0
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 0 deletions.
6 changes: 6 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ export default defineConfig({
directory: "development",
},
},
{
label: "Examples",
autogenerate: {
directory: "examples",
},
},
{
label: "Internationalization",
autogenerate: {
Expand Down
225 changes: 225 additions & 0 deletions src/content/docs/examples/airplane_entertainment_system.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
---
title: 🛫 Airplane Entertainment System
description: A sample project that simulates an airplane entertainment system.
---

import { Image, Picture } from 'astro:assets';
import airplaneEntertainmentSystemScreenshot from './images/airplane_entertainment_system.png';
import flightTrackers from './images/flight_tracker.png';
import transitionAnimation from './images/transition_animation.gif';

The Airplane Entertainment System simulates an in-flight entertainment system that provides mock flight progress updates, weather, and an audio player.

<Image src={airplaneEntertainmentSystemScreenshot} alt="Screenshot of the Airplane Entertainment FileSystem." />

The source code for this project is available on [GitHub](https://github.com/VGVentures/airplane_entertainment_system). To view the live demo, click [here](https://cuddly-doodle-kgvmnp1.pages.github.io/).

## Architecture

The Airplane Entertainment System was built using [layered architecture](../../architecture), which means that the data, repository, and presentation layers have been separated out into different packages. We will take an in-depth look at the flight tracker feature of the app to see how it was implemented using this architecture.

### Flight Tracker

<Image src={flightTrackers} alt="Screenshot of the flight tracker." width="500" />

The flight tracker simulates a flight between Newark and New York City, providing updates on the flight's progress every minute. The flight is scheduled to take off at 1:00 PM and is estimated to take 45 minutes, but the simulated delays can change the arrival time. For simplicity, a timestamp is included in the API response that begins at 1:00 PM and is incremented by one minute for each update.

#### Flight API Client

The [Flight API Client](https://github.com/VGVentures/airplane_entertainment_system/tree/main/packages/flight_api_client) emits of stream of mock flight data every minute to its listeners. In our layered architecture, the Flight API Client is part of the data layer. The API is designed to provide basic flight information so that any information that is derived from this data, like the remaining flight time, can be calculated in a different layer.

#### Flight Information Repository

The [Flight Information Repository](https://github.com/VGVentures/airplane_entertainment_system/tree/main/packages/flight_information_repository) is responsible for taking the raw data provided by the Flight API Client, applying domain business logic to the data, then providing that data to the presentation layer.

```dart
BehaviorSubject<FlightInformation>? _flightController;
/// Retrieves the flight information.
Stream<FlightInformation> get flightInformation {
if (_flightController == null) {
_flightController = BehaviorSubject();
_flightApiClient.flightInformation.listen((flightInformation) {
_flightController!.add(flightInformation);
});
}
return _flightController!.stream;
}
```

:::tip
Notice that we are using a [BehaviorSubject](https://pub.dev/documentation/rxdart/latest/rx/BehaviorSubject-class.html) here from the [rxdart](https://pub.dev/packages/rxdart) package as a stream controller. Since the repository could be used to cache the data, the BehaviorSubject is used to provide the last emitted value to any new listeners.

Check warning on line 53 in src/content/docs/examples/airplane_entertainment_system.mdx

View workflow job for this annotation

GitHub Actions / build / build

Unknown word (rxdart)
:::

#### Flight Tracking View

The [Flight Tracking](https://github.com/VGVentures/airplane_entertainment_system/tree/main/lib/flight_tracking) view consists of the UI components to display the flight information. The [FlightTrackingBloc](https://github.com/VGVentures/airplane_entertainment_system/blob/main/lib/flight_tracking/bloc/flight_tracking_bloc.dart) updates the UI with the latest information from the Flight Information Repository. This keeps all of the business logic, like fetching the data and calculating the remaining flight time, outside of the widget.

## Navigation

The Airplane Entertainment System uses bottom and side navigation bars to switch between the different tabs of the app. To maintain each tab's state, we use GoRouter's [StatefulShellRoute](https://pub.dev/documentation/go_router/latest/go_router/StatefulShellRoute-class.html). By using type-safe routes, we can setup our navigation structure in [routes.dart](https://github.com/VGVentures/airplane_entertainment_system/blob/main/lib/app_router/routes.dart).

```dart
@TypedStatefulShellRoute<HomeScreenRouteData>(
branches: [
TypedStatefulShellBranch<OverviewPageBranchData>(
routes: [
TypedGoRoute<OverviewPageRouteData>(
name: 'overview',
path: '/overview',
),
],
),
TypedStatefulShellBranch<MusicPageBranchData>(
routes: [
TypedGoRoute<MusicPlayerPageRouteData>(
name: 'music',
path: '/music',
),
],
),
],
)
```
The `HomeScreenRouteData` class is the route to our [AirplaneEntertainmentSystemScreen](https://github.com/VGVentures/airplane_entertainment_system/blob/main/lib/airplane_entertainment_system/view/airplane_entertainment_system_screen.dart) widget, which is the container for our navigation bars and content.

```dart
@immutable
class HomeScreenRouteData extends StatefulShellRouteData {
const HomeScreenRouteData();
@override
Widget builder(
BuildContext context,
GoRouterState state,
StatefulNavigationShell navigationShell,
) =>
navigationShell;
static Widget $navigatorContainerBuilder(
BuildContext context,
StatefulNavigationShell navigationShell,
List<Widget> children,
) {
return AirplaneEntertainmentSystemScreen(
navigationShell: navigationShell,
children: children,
);
}
}
```
:::note
The `routes.dart` file is used to create the generated routing code. Notice that the `static` `$navigatorContainerBuilder` function is added here so it can be provided to the `StatefulShellRoute` when the code is generated. More information about the `navigatorContainerBuilder` can be found in the [StatefulShellRoute Transition Animations](#statefulshellroute-transition-animations) section below.

Check warning on line 114 in src/content/docs/examples/airplane_entertainment_system.mdx

View workflow job for this annotation

GitHub Actions / build / build

Unknown word (statefulshellroute)
:::

`OverviewPageBranchData` and `MusicPageBranchData` classes represent your branches. `OverviewPageRouteData` and `MusicPlayerPageRouteData` classes represent the routes within the branches. Override `GoRouteData`'s `build` method to return the widget to display for the route.

```dart
@immutable
class OverviewPageBranchData extends StatefulShellBranchData {
const OverviewPageBranchData();
}
@immutable
class OverviewPageRouteData extends GoRouteData {
const OverviewPageRouteData();
@override
Widget build(BuildContext context, GoRouterState state) =>
const OverviewPage();
}
@immutable
class MusicPageBranchData extends StatefulShellBranchData {
const MusicPageBranchData();
}
@immutable
class MusicPlayerPageRouteData extends GoRouteData {
const MusicPlayerPageRouteData();
@override
Widget build(BuildContext context, GoRouterState state) =>
const MusicPlayerPage();
}
```

### StatefulShellRoute Transition Animations

<Image src={transitionAnimation} alt="Transition animation when switching between tabs." width="300" />

To add custom transition animations to your routes that are in the same navigation stack, override the `GoRouteData`'s `pageBuilder` method. Your custom animation will then be used anytime you navigate to that route.

However, when using a `StatefulShellRoute`, each tab has a separate [Navigator](https://api.flutter.dev/flutter/widgets/Navigator-class.html) for each branch. To add a transition animation when navigating between routes that are on different branches, like when switching between tabs, you must provide a custom [navigatorContainerBuilder](https://pub.dev/documentation/go_router/latest/go_router/StatefulShellRoute/navigatorContainerBuilder.html) to provide the `StatefulNavigationShell` and the children (Navigators) that are in your shell route to your "container" widget. The `StatefulNavigationShell` provides the current index of the child (Navigator) that is selected and a method to navigate to a specific child. Once you have this data, adding transition animations using [implicit animation](https://docs.flutter.dev/ui/animations/implicit-animations) widgets like `AnimatedSlide` is straightforward.

In `airplane_entertainment_system.dart`, we create a widget that manages the transition animations between the children in the `StatefulShellRoute`.

```dart
class _AnimatedBranchContainer extends StatelessWidget {
const _AnimatedBranchContainer({
required this.currentIndex,
required this.children,
});
final int currentIndex;
final List<Widget> children;
@override
Widget build(BuildContext context) {
final isSmall = AesLayout.of(context) == AesLayoutData.small;
final axis = isSmall ? Axis.horizontal : Axis.vertical;
return Stack(
children: children.mapIndexed(
(int index, Widget navigator) {
return AnimatedSlide(
duration: const Duration(milliseconds: 600),
curve: index == currentIndex ? Curves.easeOut : Curves.easeInOut,
offset: Offset(
axis == Axis.horizontal
? index == currentIndex
? 0
: 0.25
: 0,
axis == Axis.vertical
? index == currentIndex
? 0
: 0.25
: 0,
),
child: AnimatedOpacity(
opacity: index == currentIndex ? 1 : 0,
duration: const Duration(milliseconds: 300),
child: IgnorePointer(
ignoring: index != currentIndex,
child: TickerMode(
enabled: index == currentIndex,
child: navigator,
),
),
),
);
},
).toList(),
);
}
}
```

The `_AnimatedBranchContainer` widget is a custom implementation of the [StatefulShellRoute.indexedStack](https://pub.dev/documentation/go_router/latest/go_router/StatefulShellRoute/StatefulShellRoute.indexedStack.html) constructor. We must provide our own `Stack` widget to contain the children and manually update the index of the children within the `Stack` when the route changes. Since implicit animations automatically update when any of their properties change, we don't have to worry about creating custom animation objects or managing their state. Wrapping our `navigator` widget in a [TickerMode](https://api.flutter.dev/flutter/widgets/TickerMode-class.html) widget ensures that any animation tickers for the non-selected `navigator` are disabled.

To switch between tabs, simply call the `goBranch` method on the `StatefulNavigationShell` with the index of the tab you want to navigate to.

```dart
navigationShell.goBranch(
index,
initialLocation:
index == navigationShell.currentIndex,
);
```
:::note
Setting the `initialLocation` parameter to `true` will set the route to the initial location. This is sometimes the desired behavior when the user selects the tab that is already active.
:::
4 changes: 4 additions & 0 deletions src/content/docs/examples/financial_dashboard.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
title: 📈 Financial Dashboard
description: A samaple project that simulates a financial dashboard.

Check warning on line 3 in src/content/docs/examples/financial_dashboard.mdx

View workflow job for this annotation

GitHub Actions / build / build

Unknown word (samaple)
---
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/content/docs/examples/vehicle_cockpit.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
title: 🏎 Vehicle Cockpit
description: A sample project that simulates a vehicle cockpit.
---

0 comments on commit 25502a0

Please sign in to comment.