Airplane Entertainment System
The Airplane Entertainment System simulates an in-flight entertainment system that provides mock flight progress updates, weather, and an audio player.
The source code for this project is available on GitHub. To view the live demo, click here.
Architecture
The Airplane Entertainment System was built using layered architecture. In the interest of a well organized project, the data, repository, and presentation layers have been separated out into their own 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
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 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 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.
Flight Tracking View
The Flight Tracking view consists of the UI components to display the flight information. The FlightTrackingBloc 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. By using type-safe routes, we can setup our navigation structure in routes.dart.
The HomeScreenRouteData
class is the route to our AirplaneEntertainmentSystemScreen widget, which is the container for our navigation bars and content.
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.
StatefulShellRoute Transition Animations
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 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 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 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
.
The _AnimatedBranchContainer
widget is a custom implementation of the StatefulShellRoute.indexedStack 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 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.