Flutter : Where is Cupertino table view ?

David Gonzalez
Flutter Community
Published in
9 min readJan 23, 2021

In Flutter SDK, there is no widget that mimics the iOS UITableView component (yet ?), with a floating section, as shown below.

UX behavior of the iOS UITableView made in Swift (look at “Section 3” pushing away “Section 2”)

In iOS, the UITableView let you specify data grouped into sections. By default, the ui component style is plain, meaning that section title “are displayed as inline separators and float when the table view is scrolled”.

Therefore I searched in pub package a library that let you create that kind of list, and I found plenty of candidate.

Depending on libraries description, I selected 6 candidates to test:

My brain, understanding the amount of work.

Tests methodology

For every library, I will use a specific set of data: we will have 10 sections containing a random number of items between 5 to 15. And for every two items in a section, row data will be repeated 10 times to simulate big cells.

That set of data will be the same for each library widget.

For each tested library, I will show you the source code and result (as a gif)

The tested criteria are:

  • (Smoke test) : the current section header is floating
  • Current section header is pushed by the next one without any overlaps when the user is scrolling
  • Every items of section are properly drawn (full length, full text)
  • Reliability of the widget with rows having unknown height
  • Good API and documentation (description of input parameters in English at least)
Now we can start our experiments !

group_list_view library

The code is pretty much easy to understand, and it’s well documented.

I’m quibbling, but there is only one little mistake in the API design, as section and groupHeader is the same thing. Why not sectionBuilder instead of groupHeaderBuilder ?

Widget _buildGroupListView() {
return GroupListView(
sectionsCount: widget.data.length,
countOfItemInSection: (section) => widget.data[section].count,
groupHeaderBuilder: (context, index) {
final section = widget.data[index];
return __buildHeader(name: section.name, icon: section.icon);
},
itemBuilder: (context, path) {
final data = widget.data[path.section][path.index];
return __buildData(name: data.name, value: data.value);
},
);
}

Also, it is well documented.

But the result is not as expected, as there is no way to make the section header floating.

There is no section animation.

In my opinion, it may be easier to create a listview with two kind of list tile instead of using this library.

Verdict: this library does not pass the smoke test, that’s not what we want. So we won’t go further.

flutter_section_list_view library

Like previous library, this is very easy to build sections and rows…

Widget _buildFlutterSectionListView() {
return FlutterSectionListView(
numberOfSection: () => widget.data.length,
numberOfRowsInSection: (section) => widget.data[section].count,
sectionWidget: (index) {
final section = widget.data[index];
return __buildHeader(name: section.name, icon: section.icon);
},
rowWidget: (section, index) {
final data = widget.data[section][index];
return __buildData(name: data.name, value: data.value);
},
);
}

...But like the previous one, the result fails the smoke test.

Still no section pushed away by the next one

Also the API is not comfortable, because we cannot set common listview input parameters such as ScrollController.

Verdict: that’s not what we want (again)!

grouped_list library

Well, let’s say I’m not comfy with that library, in particular with the API.

Widget _buildGroupedListView() {
return GroupedListView<Map<String, dynamic>, String>(
floatingHeader: true,
useStickyGroupSeparators: true,
elements: widget.data
.map((e) => e.flatMap())
.fold([], (list, elt) => list + elt),
groupBy: (element) => element['name'],
itemComparator: (map1, map2) => map1['dataValue'] - map2['dataValue'],
itemBuilder: (context, map) =>
__buildData(name: map['dataName'], value: map['dataValue']),
groupHeaderBuilder: (map) =>
__buildHeader(name: map['name'], icon: map['icon']),

);
}

At the contrary of other libraries, you have to specify elements type, and functions to group and sort items (respectively groupBy and itemComparator input parameters).

A good usecase may be when we retrieve JSON list data that will be displayed as a grouped list. In our case, it just doesn’t fit.

Consequently It is a little bit more complex to use, but provided documentation is detailed enough to understand what’s to be implemented. Also, we have these two parameters, floatingHeader and useStickyGroupSeparators, so we can hope that the section title will float!

It seems to animate section titles…

We can observe that the current section is floating, but not pushed away by the next one. At the end of the GIF, you can see that the two sections have the same title and icon.

I can understand difficulty to make such a widget, but in that case it doesn’t reach the quality I expect.

Verdict: There is a case where the UI fault, very easy to reproduce, so you should avoid using it.

linkageview library

This is the most complex library I tested for that article. There is no documentation in English, so I only used parameters name to understand what I had to implements.

Widget _buildLinkageView() {
final baseItemBuilder = (BaseItem item) {
if (item.isHeader) {
return __buildHeader(name: item.header, icon: item.info);
}
return __buildData(name: item.title, value: item.info);
};
return LinkageView<BaseItem>(
isNeedStick: true,
items: widget.data
.map((e) => e.toBaseItems())
.fold([], (list, elt) => list + elt),
itemBuilder: (_, __, item) => baseItemBuilder(item),
headerBuilder: (_, item) =>
__buildHeader(name: item.header, icon: item.info),
curve: Curves.elasticInOut,
groupItemBuilder: (_, __, item) => Icon(
item.info,
color: Colors.blue,
),
itemHeadHeight: 70.0,
onGroupItemTap: (_, __, item) =>
print('Selected section: ${item.header}'),
duration: 500, // milliseconds
);
}

The API is not straightforward, as I were not able to understand the difference between headerBuilder and groupItemBuilder, without testing it first. Also, I don’t understand why there is itemBuilder and headerBuilder, because itemBuilder actually build section and row widgets…

I think that this library is a POC, because it serve only the person who implemented it: crashes occurs if you don’t set all the inputs parameters, even if they are not marked required.

Added to that, there are annoying development shortcuts for Flutter developers. For example, we expect duration to be an instance of Duration.

This library provide a widget for a very specific usecase…

As you may already noticed in the code, we have to specify item height to make it works. At the end of the GIF above, you can also see that the current section is not properly pushed away, because I voluntarily set an inappropriate item height. Rows are not extending correctly, as it should be (it may mimics incorrectly the ListView itemExtent input parameter).

I think that may be time consuming to set the right item height to have a perfect animation… Maybe impossible if we cannot set a unique height for all rows.

Verdict: this widget is too specific for our needs, and is difficult to use.

flutter_tableview library

Well, as most of other libraries, this one is very easy to understand and use.

Widget _buildFlutterTableView() {
return FlutterTableView(
sectionCount: widget.data.length,
rowCountAtSection: (section) => widget.data[section].count,
sectionHeaderBuilder: (_, index) {
final section = widget.data[index];
return __buildHeader(name: section.name, icon: section.icon);
},
cellBuilder: (_, section, index) {
final data = widget.data[section][index];
return __buildData(name: data.name, value: data.value);
},
sectionHeaderHeight: (_, __) => 50.0,
cellHeight: (_, section, index) => 40.0,
);
}

The only sad thing is that you have to specify sectionHeaderHeight and cellHeight (required input parameters) to make it works. It means that if you don’t know cells height, it will be difficult for you to guaranty a perfect design.

I could have set a different height for cells having a big data, but it just get around the widget limitations

That means if you want a prefect design, you will have to compute cell height before actually layout phase. In other terms, you can trick it but it will cost a lot of CPU (and maybe GlobalKey objects). I don’t think an approach like that is correct.

Conclusion : The animation is correct, but at a cost. If you want a global solution, capable of displaying cells having different heights, then you may avoid this library. On contrary, if you are sure that cells height are fixed, then it will do the job correctly.

flutter_section_list library

That library gave the best results. Unlike the others, the developers of that library force them to use a SectionAdapter that will handle the display of elements (sections and items).

Widget _buildSectionListView() {
return fsl.SectionListView.builder(
adapter: SectionListViewAdapter(
data: widget.data,
sectionBuilder: (name, icon) => __buildHeader(name: name, icon: icon),
itemBuilder: (name, value) => __buildData(name: name, value: value),
),
);
}

And the code of my custom adapter is here:

class SectionListViewAdapter with fsl.SectionAdapterMixin {
final List<DataTestGroup> data;
final Widget Function(String name, IconData icon) sectionBuilder;
final Widget Function(String name, int value) itemBuilder;

SectionListViewAdapter({this.data, this.sectionBuilder, this.itemBuilder});

@override
int numberOfSections() => data.length;

@override
Widget getItem(BuildContext context, fsl.IndexPath indexPath) {
final item = data[indexPath.section][indexPath.item];
return itemBuilder(item.name, item.value);
}

@override
Widget getSectionHeader(BuildContext context, int section) {
final item = data[section];
return sectionBuilder(item.name, item.icon);
}

@override
int numberOfItems(int section) => data[section].count;

@override
bool shouldExistSectionHeader(int section) => true;

@override
bool shouldSectionHeaderStick(int section) => true;
}

The documentation is not in English, but API is very straightforward to use, as SectionListView mimics ListView (input parameters), and Adapter is pretty much the same as the others libraries. It’s pretty much the same thing as UITableViewSource in the iOS world.

Most of the time, rendering is perfect, but with usages I ended with this :

I don’t know what is provoking that artifact

To me, that was the most tricking library, as it works very well for a certain amount of time, then it starts making position errors. Testing on simulators could be part of the problem, but tests were not meticulous, so I fear to discover other artifacts…

I’m so sad that it not as reliable as expected, because after having inspected the source code, I think that the approach was good. The developer(s) implemented their own ListView (maybe starting by copy-paste ListView source code, then applying modifications). That’s what I would have done, but It represents a lot of code, with some difficulties to maintain (especially if ListView and underneath classes are evolving).

Conclusion: I’m not convinced that it is the library to use for applications having strong community, because sooner or later this artifact will appears.

Conclusion

As suggested in the title, there is no perfect implementation of the iOS UITableView component. Some are very closed to it, but there is some limitations or artifacts that can lead to errors in UI/UX design.

This close to have the perfect widget!

To me, if you have not a big quality requirements, then it’s worth to try flutter_section_list or flutter_tableview libraries.

But I have more expectations about that widget (reliability, code maintenance, no row height limitations). So I developed a new library, that I will provide a demonstration in another article. You can try it here (and give me feedback): https://pub.dev/packages/cupertino_listview.

As always, you will be able to find the source code of the study in my github:

Follow Flutter Community on Twitter: https://www.twitter.com/FlutterComm

--

--

David Gonzalez
Flutter Community

Hi, I’m David, a french mobile applications developer. As a freelance, I work on Android and iOS applications since 2010. I also work on Flutter now !