Provide the settings screen
The settings screen role is provided by a module with a route that shows the screen, so the role requires the router. The modules of the app, and the templates of roles, give the role the entries of the screen, and the provider shows them. How settings work describes the role, and Add a setting the side of a module with an entry.
The module and its provider
This is what settings, a feature, contributes:
List<Contribution> contribute(ModuleContext context) => [
BrickContribution(
settingsBundle,
vars: localizationRole.varsOf(id, _texts),
),
localizationRole.data(_texts),
routerRole.data(
const RoutesData([
Route(
'/',
name: _route,
screen: ScreenRef(
'SettingsScreen',
import: ImportRef.app('features/settings/settings_screen.dart'),
),
destination: Destination(
label: _titleText,
icon: Fragment(
'Icons.settings',
imports: [
ImportRef('package:flutter/material.dart', show: ['Icons']),
],
),
),
),
]),
),
settingsScreenRole.data(const SettingsScreenRoute(_route)),
AppEntryRole.agentSections.entry(
settingsScreenRole.description,
AgentNote(agentNote),
),
];
The title of the screen is a text of the module, _titleText, which is the label of its destination too. Its provider renders the entries of the role, each with an import of its file, and tells the brick whether the app has any:
final class _SettingsProvider extends RoleProvider<SettingsData> {
const _SettingsProvider();
Role<SettingsData> get role => settingsScreenRole;
/// Two variables of the brick of the screen:
/// - `entries`, the widgets of the entries as the items of a constant
/// list, one on a line, in the order of the role, with the imports of
/// their files, each with a prefix of its own: `entry0` for the first
/// file, `entry1` for the next. Without entries, the variable has no
/// code, and its line of the template goes away.
/// - `with_entries`, whether the app has an entry. The template has the
/// group of the entries in an app with one, and the note of a screen
/// without settings in an app with none, so no app gets the code of
/// the other screen.
RoleOutput render(RoleHookInput<SettingsData> input) {
// The import of each file of an entry, with its prefix, by the path of
// the file.
final files = <String, ImportRef>{};
final items = <String>[];
for (final entry in settingsScreenRole.entriesIn(input)) {
final widget = entry.widget;
// The template of the role rejects an entry whose widget is not in a
// file of the app, so each has an import.
final import = widget.import!;
final prefix = files
.putIfAbsent(
import.uri,
() => import.withPrefix('entry${files.length}'),
)
.prefix;
items.add(' ${widget.codeWith(prefix)}(),');
}
return RoleOutput(
vars: {
'with_entries': items.isNotEmpty,
'entries': Fragment(items.join('\n'), imports: [...files.values]),
},
);
}
}
The template of the screen reads the entries as a fragment variable, inside a constant list. That list is in the section of with_entries, so only an app with entries gets the group:
bottom: false,{{#with_entries}}
child: ListView(
padding: const EdgeInsets.fromLTRB(16, 20, 16, 32),
children: [
title,
const SizedBox(height: 20),
const _Group(
children: [{{/with_entries}}
{{{entries}}}
{{#with_entries}} ],
),
],
),{{/with_entries}}{{^with_entries}} child: CustomScrollView(
An app without entries gets what follows {{^with_entries}} instead: a note that tells the developer of the app that it has no settings yet, with the path of the file of the screen. The provider knows whether the app has entries when it renders them, so each app gets the code of its own screen and nothing of the other. _Group is a class of the same file, a card with the entries one below the other and a line between them. settings shows the file of an app.
What every provider promises
- It names exactly one route of its own as the screen, with
SettingsScreenRoute. The route needs no values, so that code which knows only the role can go to the screen. - The screen shows every entry of
settingsScreenRole.entriesIn(input)once, in that order: the entries of the modules in the order the modules were chosen, then those of the templates of roles. - It shows the entries one below the other in a list that scrolls, on a
Material. It sets the width of each entry, the same for every entry between the same left and right edges, and puts no limit on its height. - It creates the widget of each entry in a file other than that of the widget, through an import of that file with a prefix that no import of another file has, such as
entry0. So widgets of the same name in the files of different modules do not clash, with each other or with the names of the file of the provider.
How the user gets to the screen is up to the provider. settings makes its route a destination of the main navigation.
What is checked
smf create checks the route before it generates an app. The contract harness also checks that the files of the provider create the widget of every entry through such an import. SMF's CI checks the rest in running apps: the route shows the screen, and the screen shows each entry once, in order, in a list that scrolls; see Contributing.