Cupertino lists, the missing widgets from the Flutter Cupertino collection

Cupertino lists, the missing widgets from the Flutter Cupertino collection

Exploring the new Cupertino widgets for creating lists: CupertinoListTile and CupertinoListSection

Although Flutter provides an easy mechanism to be able to create the UI once for all platforms, there are times when it is desirable to closely tweak the visuals to match the native elements of the platform. We might want to do this so the user feels at home and thus prevent your hybrid application from being the 'weird one' on your users phones. This is especially important if you include the entire collection of Material widgets on iOS, as iOS users are not used to its visual style, its color system, and its animations.

In order to provide tools to mitigate this to developers, Flutter provides the Cupertino collection of widgets that try to mimic the visual style and behavior of native iOS elements. If you look carefully at the list of Cupertino widgets you will see that there is a huge lists of elements; CupertinoPageScaffold as the main parent of the screens, CupertinoButton for the regular buttons, CupertinoDatePicker for the date selection, etc. However, there is one type of element that does not exist: lists.

And why they didn't include such an important element? The truth is that I don't know the reason why this was not included from the beginning, given that almost every app out there have some kind of list. However, this will be fixed soon, since the CupertinoListTile and CupertinoListSection widgets have already been merged in the master branch. These widgets will allow us to completely replace the ListTile widget to give it that iOS-style touch that some applications need.

Let's see what these new widgets look like and what possibilities they give us.

📽 Video version available on YouTube and Odysee

CupertinoListTile & CupertinoListSection

If you want to try these widgets for yourself, the first thing you have to do is switch to the master channel. This is because these widgets have not yet been included in any official Flutter release:

flutter channel master && flutter upgrade

Now we will create a new application whose only target is iOS:

flutter create --platforms ios cupertino_lists

Open main.dart and remove the boilerplate code, we will also take the opportunity to replace the Material widgets with the Cupertino ones:

import 'package:flutter/cupertino.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      title: 'Cupertino Lists',
      home: MainScreen(),
    );
  }
}

class MainScreen extends StatelessWidget {
  const MainScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(
        middle: Text('Cupertino Lists'),
      ),
      child: Container(),
    );
  }
}

We are left with a simple empty screen:

Empty screen

We are going to include a list of elements of type CupertinoListTile. These elements must be encapsulated within a CupertinoListSection; if you try to add them in a ListView or similar you will see that they don't look as expected:

class MainScreen extends StatelessWidget {
  const MainScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(
        middle: Text('Cupertino Lists'),
      ),
      child: SafeArea(
        child: CupertinoListSection(
          children: List.generate(
            10,
            (index) => CupertinoListTile(
              title: Text('Element $index'),
            ),
          ),
        ),
      ),
    );
  }
}

Notice that I have included a SafeArea widget before the CupertinoListSection. This widget is especially useful in the Cupertino environment so that space is automatically left for the navigation bar and for the bottom edge of the screen on the latest iPhone models that do not have a physical button. We are left with an iOS-style list:

Simple list

If you try to add more elements to the list (by modifying the first parameter of the generate() method) you will see that the rendering fails. This is because this list is not yet scrollable. We can easily fix this if we wrap the CupertinoListSection inside a widget of the type SingleChildScrollView:

class MainScreen extends StatelessWidget {
  const MainScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(
        middle: Text('Cupertino Lists'),
      ),
      child: SafeArea(
        child: SingleChildScrollView(   // Make the list scrollable
          child: CupertinoListSection(
            children: List.generate(
              50,
              (index) => CupertinoListTile(
                title: Text('Element $index'),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

CupertinoListTile has several configuration options to be able to apply different effects to each element of the list:

class MainScreen extends StatelessWidget {
  const MainScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(
        middle: Text('Cupertino Lists'),
      ),
      child: SafeArea(
        child: SingleChildScrollView(
          child: CupertinoListSection(
            children: const [
              CupertinoListTile(title: Text('Simple tile')),
              CupertinoListTile(
                title: Text('Title of the tile'),
                subtitle: Text('Subtitle of the tile'),
              ),
              CupertinoListTile(
                title: Text('With additional info'),
                additionalInfo: Text('Info'),
              ),
              CupertinoListTile(
                title: Text('With leading & trailing'),
                leading: Icon(CupertinoIcons.add_circled_solid),
                trailing: Icon(CupertinoIcons.chevron_forward),
              ),
              CupertinoListTile(
                title: Text('Different background color'),
                backgroundColor: CupertinoColors.activeGreen,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

tile_effects.png

If you have seen the latest visual styles of the lists of native iOS applications (especially system applications) you will have noticed that these include side margins and rounded edges. Fortunately, CupertinoListSection includes a secondary constructor to achieve this effect:

CupertinoListSection.insetGrouped()

Inset grouped

To finish, we can add a widget as a header and also one as a footer:

// [...]
child: CupertinoListSection.insetGrouped(
  header: const Text('List header'),
  footer: const Text('List footer'),
// [...]

header_footer.png

When will it be available on the stable channel?

Judging by the information in the commit where it was merged, everything seems to indicate that both widgets will arrive in the next Flutter release, until then we will have to continue depending on ListTile, or the existing plugins that have already tried to solve this problem.

That's all for today, I hope you found it useful, thanks for reading this far and happy coding :)

Did you find this article valuable?

Support David Serrano by becoming a sponsor. Any amount is appreciated!