I'm learning the Flutter ropes. The tutorial was great, but when I tried to replace the hardcoded sidebar switch and NavigationRail with ones generated from a list, I ran into a bunch of typing trouble. Basically, I want to generate them from this:
var appPages = [
{
'name': 'Header 1',
'icon': const Icon(Icons.cloud_upload),
'content': const PlaceholderPage(
placeholderText: '<h1>Test Header 1</h1><br/>Lorem ipsum.')
},
{
'name': 'Header 2',
'icon': const Icon(Icons.delete_sweep_outlined),
'content': const PlaceholderPage(
placeholderText: '<h2>Test Header 2</h2><br/>Lorem ipsum.')
},
{
'name': 'Bold Italics',
'icon': const Icon(Icons.bloodtype),
'content': const PlaceholderPage(
placeholderText: '<i>Test <b>Bold</b> Italics</i><br/>Lorem ipsum.')
},
];
Then instead of
Widget page;
switch (selectedIndex) {
case 0:
page = const PlaceholderPage(placeholderText: '<h1>Test Header 1</h1><br/>Lorem ipsum.');
break;
case 1:
page = const PlaceholderPage(placeholderText: '<h2>Test Header 2</h2><br/>Lorem ipsum.');
break;
case 2:
page = const PlaceholderPage(placeholderText: '<i>Test <b>Bold</b> Italics</i><br/>Lorem ipsum.');
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
I would have:
var page = appPages[selectedIndex]['content'] as Widget?;
And instead of listing each NavigationRailDestination:
destinations:
const [
NavigationRailDestination(
icon: Icon(Icons.cloud_upload),
label: Text('Header 1')),
NavigationRailDestination(
icon: Icon(Icons.delete_sweep_outlined),
label: Text('Header 2')),
NavigationRailDestination(
icon: Icon(Icons.bloodtype),
label: Text('Bold Italics')),
],
I'd have something like:
destinations:
appPages.map((e) => {
NavigationRailDestination(
icon: e['icon'] as Widget, label: Text(e['name'] as String))
}).toList() as List<NavigationRailDestination>
But currently this fails with
_TypeError (type 'List<Set<NavigationRailDestination>>' is not a subtype of type 'List<NavigationRailDestination>' in type cast)
Any fix for this error, or a more elegant way to do it?
Thanks in advance!
pubspec.yaml:
name: sidebar_test
publish_to: 'none'
version: 0.1.0
environment:
sdk: '>=3.3.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
provider: ^6.0.0
material_design_icons_flutter: ^7.0.0
styled_text: ^8.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
flutter:
uses-material-design: true
main.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:styled_text/styled_text.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
var appName = 'Sidebar Test';
var appTheme = ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0x006AAE3F)),
);
var appStyleTags = {
'h1': StyledTextTag(
style: TextStyle(
height: 2, fontSize: 50, color: appTheme.colorScheme.primary)),
'h2': StyledTextTag(
style: appTheme.textTheme.displayMedium!.copyWith(
color: appTheme.colorScheme.primary,
)),
'b': StyledTextTag(style: const TextStyle(fontWeight: FontWeight.bold)),
'i': StyledTextTag(style: const TextStyle(fontStyle: FontStyle.italic)),
};
var appPages = [
{
'name': 'Header 1',
'icon': const Icon(Icons.cloud_upload),
'content': const PlaceholderPage(
placeholderText: '<h1>Test Header 1</h1><br/>Lorem ipsum.')
},
{
'name': 'Header 2',
'icon': const Icon(Icons.delete_sweep_outlined),
'content': const PlaceholderPage(
placeholderText: '<h2>Test Header 2</h2><br/>Lorem ipsum.')
},
{
'name': 'Bold Italics',
'icon': const Icon(Icons.bloodtype),
'content': const PlaceholderPage(
placeholderText: '<i>Test <b>Bold</b> Italics</i><br/>Lorem ipsum.')
},
];
var appPagesFirst = appPages[0];
// var appPagesList = appPages.entries.toList();
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: appName,
theme: appTheme,
home: const HomePage(),
debugShowCheckedModeBanner: false,
restorationScopeId: appName,
),
);
}
}
class MyAppState extends ChangeNotifier {}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
var selectedIndex = 0;
bool? navExtended;
@override
Widget build(BuildContext context) {
var colorScheme = Theme.of(context).colorScheme;
// Use appPages
var page = appPages[selectedIndex]['content'] as Widget?;
// Instead of switch:
// Widget page;
// switch (selectedIndex) {
// case 0:
// page = const PlaceholderPage(placeholderText: '<h1>Test Header 1</h1><br/>Lorem ipsum.');
// break;
// case 1:
// page = const PlaceholderPage(placeholderText: '<h2>Test Header 2</h2><br/>Lorem ipsum.');
// break;
// case 2:
// page = const PlaceholderPage(placeholderText: '<i>Test <b>Bold</b> Italics</i><br/>Lorem ipsum.');
// break;
// default:
// throw UnimplementedError('no widget for $selectedIndex');
// }
// The container for the current page, with its background color
// and subtle switching animation.
var mainArea = ColoredBox(
color: colorScheme.surfaceVariant,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: page,
),
);
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) {
return Row(
children: [
SafeArea(
child: NavigationRail(
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
setState(() {
selectedIndex = value;
});
},
extended: navExtended != null
? navExtended ?? true
: constraints.maxWidth >= 600,
leading: StyledText(
text: '<b>$appName</b>',
tags: appStyleTags,
),
trailing: Expanded(
child: Align(
alignment: Alignment.bottomLeft,
child: IconButton(
icon: Icon((navExtended ?? true)
? MdiIcons.arrowCollapseLeft
: MdiIcons.arrowExpandRight),
onPressed: () {
setState(() {
setState(
() => navExtended = !(navExtended ?? true));
});
},
),
),
),
destinations:
// Use appPages
appPages.map((e) => {
NavigationRailDestination(
icon: e['icon'] as Widget, label: Text(e['name'] as String))
}).toList() as List<NavigationRailDestination>
// Instead of
// const [
// NavigationRailDestination(
// icon: Icon(Icons.cloud_upload),
// label: Text('Header 1')),
// NavigationRailDestination(
// icon: Icon(Icons.delete_sweep_outlined),
// label: Text('Header 2')),
// NavigationRailDestination(
// icon: Icon(Icons.bloodtype),
// label: Text('Bold Italics')),
// ],
),
),
Expanded(child: mainArea),
],
);
// }
},
),
);
}
}
class PlaceholderPage extends StatelessWidget {
const PlaceholderPage({
super.key,
required this.placeholderText,
});
final String placeholderText;
@override
Widget build(BuildContext context) {
return Column(children: [
const Spacer(),
Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: AnimatedSize(
duration: const Duration(milliseconds: 200),
child: StyledText(text: placeholderText, tags: appStyleTags))),
),
const Spacer(),
]);
}
}
Dart's anonymous functions are defined with parentheses and curly braces: (s) { print(s); }
, but if it consists of a single statement, you can use right arrow ((s) => print(s);
, and then you must omit the curly braces or things will break, or worse, be interpreted as a Set. D'oh.
Removing them (or the arrow) was all that was needed:
destinations:
appPages.map((e) =>
NavigationRailDestination(
icon: e['icon'] as Widget, label: Text(e['name'] as String))
).toList() as List<NavigationRailDestination>
Working sandbox: https://flutlab.io/editor/2041e217-a3b9-482f-bc3e-45c0a36895fd