Bottom Navigation

Setup

Let's create a template Flutter project and download the files and put them in the correct place, following these paths:

Install dependencies:

pubspec.yaml
dependencies:
...
  paper: ^1.0.0
  flutter_localizations:
    sdk: flutter
  intl: any

flutter:
  ...
  generate: true

In the lib/main.dart, replace the content with the following:

lib.main.dart
import 'package:paper/paper.dart';

import 'src/unit_widget/app/app.dart';

void main() {
    final app = WidgetAgent<AppPaper>();
    PaperFrameworkEstablisher().initializeWidgetUnit((s) => App(s, agent: app));
}

We can run the command $ flutter gen-l10n to generate localization and run. Or just run without generating.

Run it and we have:

Breakdown

Bottom Navigation diagram.

Localizations

The AppLocalizations should be considered as a Model. It is immutable and acts as a data structure for Localizations.

And as suggested before, the observable-sit-on-top architecture should not be applied in this pattern. Using AppLocalizations.of(context) to retrieve translations is prohibited. The translations have to be passed down via constructor to objects that need them. This also applies to Safe area's padding.

Decor

The TextDecor and ColorDecor extend from Model. They are designed as singletons because there should be one instance of each to define the text theme and color for the UI. There is no need to create multiple instances. And because they are singletons, they have their keys hardcoded as there would be no comparison because again there is only one instance throughout the app lifecycle.

App

The App is the very top unit staying above every unit in the app structure. If the app is a company, the App is the CEO or the top manager.

The App extends from Unit

PageView

When we swipe between pages, the PageView notifies the App via triggering a listener - onPageChanged, in which the App modifies the index to update the BottomBar, or we can say the App has given a "command" to the BottomBar to update its index.

BottomBar

Similar to PageView, when the BottomBar's index is changed by user tapping on the icons, the BottomBar notifies the App via triggering a listener - onChanged, from which the App has given a "command" to the PageView to update its index.

Paper

The AppCurrentTab paper represents a request to the App to update the current index of both BottomBar and PageView based on AppCurrentTab.index

Note
// lib/src/unit_widget/app/app_script.dart

void onCurrentTab(AppCurrentTab p, AppState s, SourceVerifier ifFrom) {
    ifFrom(s.bottomBarController)?.setIndex = p.index;
    s.setPage(ifFrom(s.pageController), p.index);
}

Why do we have to create and use AppState.setPage instead of directly using PageController.animateToPage?

The problem lies in the PageController itself. When we use PageController.animateToPage, it also notifies the App via onPageChanged as well. It does NOT make sense, especially in this pattern. Imagine A gives a command to B to change its state, for example a number from 0 to 1, and then B immediately notifies back to A that its number just changed to 1. It will probably create a loop and A commands B, B notifies back to A, then A commands B again, and so on.

This issue also occurs with other "controllers" in the Flutter library: TextEditingController, ScrollController, etc. And we are going to re-design widgets to which those controllers attach.

Just keep in mind that when a parent gives a "command" to its children, those actions must not lead to backward notification about the changes that were made by those commands.