On Part 1, I made the working version of the Escapists Crafting Guide app using Flutter. I used ListView widget to show the different tools and items available for crafting.
Today I am going to improve the app slightly by showing on the list the category where these tools belong. I am also going to replace the ListView with SliverList — I describe it as a fancier list view. I am also exploring flutter_sticky_header[repository] [pub.dev] to achieve the behavior when the user scrolls and the category sticks to the top until the next category is in the current view. I will also be polishing the design along the way.

Categories of tools source: Gamepedia The Escapists Crafting Guide
SliverList
To use the SliverList, it needs to be wrapped on a CustomScrollView which comes with the familiar API that we already learned from using ListView with slight changes. This widget requires a list of children widgets called slivers.
A sliver is a portion of a scrollable area. A sliver could be a SliverAppBar, SliverList, or a SliverGrid.
| CustomScrollView( | |
| slivers: [ | |
| SliverAppBar( | |
| flexibleSpace: FlexibleSpaceBar( | |
| title: Text("Escapists Craft"), | |
| ), | |
| ), | |
| SliverList( | |
| delegate: SliverChildBuilderDelegate( | |
| (BuildContext context, int index) { | |
| /// For dynamic content | |
| } | |
| ), | |
| ), | |
| ] | |
| ) |
Data structure
Previously, I was just working with a linear list of all items. Right now I need to refactor it to group the tools by categories.
My input data contains a list of items, and for each item it has a category.
[
{
"name": "Flimsy Pickaxe",
"category": "Tools"
},
{
"name": "Lightweight Pickaxe",
"category": "Tools",
},
{
"name": "Cup of Molten Chocolate",
"category": "Weapon"
}
]
A function is required to create a map data structure of the tools. The map will then be used to build the slivers. The slivers will contain the category as the header — the one that sticks to the top when scrolled, and then followed by the rest of the items belonging to the category.
Map categorizeItems(items) {
var categories = Map();
items.forEach((element) {
if (!categories.containsKey(element.category)) {
categories[element.category] = List();
}
categories[element.category].add(element);
});
return categories;
}
Sticky header
I am using flutter_sticky_header to achieve the sticky header effect. First, update pubspec.yaml.
dependencies:
flutter_sticky_header: ^0.5.0
Then run flutter pub get.
The basic structure looks like below. Each one of this is a child of the CustomScrollView.slivers
SliverStickyHeader(
header: Header(title: category),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return InkWell(
child: ItemCard(data: items[index]),
onTap: () {...}
);
},
childCount: items.length,
), // SliverChildBuilderDelegate
), // SliverList
)
The resulting code looks like the following.
| class _MyHomePageState extends State<MyHomePage> { | |
| Map categorizeItems(items) { | |
| var categories = Map(); | |
| items.forEach((element) { | |
| if (!categories.containsKey(element.category)) { | |
| categories[element.category] = List(); | |
| } | |
| categories[element.category].add(element); | |
| }); | |
| return categories; | |
| } | |
| List<Widget> buildSlivers(itemsMap) { | |
| List<Widget> slivers = []; | |
| itemsMap.forEach((category, items) { | |
| slivers.add(SliverStickyHeader( | |
| header: Header(title: category), | |
| sliver: SliverList( | |
| delegate: SliverChildBuilderDelegate( | |
| (BuildContext context, int index) { | |
| return InkWell( | |
| child: ItemCard(data: items[index]), | |
| onTap: () { | |
| Navigator.push( | |
| context, | |
| MaterialPageRoute( | |
| builder: (context) => DetailPage( | |
| item: items[index], | |
| ), | |
| ), | |
| ); | |
| }, | |
| ); | |
| }, | |
| childCount: items.length, | |
| ), | |
| ), | |
| )); | |
| }); | |
| return slivers; | |
| } | |
| // Widget build(BuildContext context) ... | |
| // Build the widget with data. | |
| Map categorizedItems = categorizeItems(_filteredItems); | |
| var categorizedSlivers = buildSlivers(categorizedItems); | |
| return Scaffold( | |
| body: Container( | |
| color: Colors.white, | |
| child: CustomScrollView( | |
| slivers: [ | |
| SliverAppBar( | |
| pinned: true, | |
| flexibleSpace: FlexibleSpaceBar( | |
| title: Text("Escapists Craft"), | |
| ), | |
| backgroundColor: Colors.black, | |
| textTheme: TextTheme( | |
| headline6: TextStyle( | |
| color: Colors.black, | |
| fontSize: 36.0, | |
| ), | |
| ), | |
| ), | |
| SliverAppBar( | |
| floating: true, | |
| snap: true, | |
| flexibleSpace: FlexibleSpaceBar( | |
| title: SearchBar( | |
| searchTextController: searchTextController), | |
| ), | |
| backgroundColor: Colors.white, | |
| toolbarHeight: 10.0, | |
| ), | |
| ...categorizedSlivers, // list of tools with categories | |
| ], | |
| ), | |
| ), | |
| ); | |
| } |
Conclusion
Now, I have finally ticked off an improvement. Will continue to do more.
- A separator between categories on the list view
Next, I will be using another sliver to achieve a feature that I always wanted to have — a complete spread or a tree, not just the immediate requirements, to show all components required to craft a tool.
Until next time!
References
- SliverList class https://api.flutter.dev/flutter/widgets/SliverList-class.html
- Using slivers to achieve fancy scrolling https://flutter.dev/docs/development/ui/advanced/slivers
- Dart Maps https://dart.dev/guides/language/language-tour#maps
