0

Woah! I've spent several hours refactoring nested ListViews to a parent CustomScrollView & child Slivers. The errors Slivers produced were opaque and frightening, with nothing illuminating in Logcat; sleuthing devoured much of the time.

Anyway, that's solved. I find I still have jank scrolling a 15-item list. OK, each item can involve further numerous widgets {Padding, Alignment, Elevated button, Row, Text, SizedBox, Icon}. So my 15-item list ends up being multiple more Widgets.

I've now swapped out my SliverChildListDelegate for SliverChildBuilderDelegates, so a Builder builds the Widget List lazily. Having done this, it seems quite inefficient because it's increased the Widgets in the Widget tree. Each of the builders' buildItem() calls needs an extra Column Widget to wrap that sub-chunk of the total list.

It may be a lot of Widgets scrolling but it's only a 15 item list. I wish I knew what to optimise. Any thoughts on how to best reduce jank on Lists for mobile web?

The Flutter team says Flutter works best for computational-centred apps rather than text heavy informational apps. In future would it be better just to use webView Widgets? I always thought embedding Webviews would be clunky and slow but Lists of native Flutter Widgets, even as SliverLists, give jank.

Here is the janky list complete with builder:

  Widget buildLocationDescriptionWidgets(LocationDetails presentLocation) {
    print(LOG + "buildLocationDescriptionWidgets");
    if (presentLocation.descriptionLinkUrls.isEmpty)
      return SliverToBoxAdapter(child:
      Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
          child: Text(presentLocation.description[0])));

    int numDescriptionBlocks = presentLocation.description.length;


    double paddingBottom = 16;
    if (presentLocation.descriptionLinkUrls.length >= numDescriptionBlocks) {
      paddingBottom = 0;
    }

    return SliverPadding(
        padding: EdgeInsets.fromLTRB(16, 16, 16, paddingBottom), sliver:
    SliverList(
      key: Key("descriptionSliverList"),
      delegate: SliverChildBuilderDelegate((context, index) =>
          buildDescriptionBlock(context, index),
          childCount: presentLocation.description.length
      ),
    ));
  }

  Column buildDescriptionBlock(BuildContext context, int index) {
    List<Widget> colChildWidget = [];
    colChildWidget.add(Text(
      widget.presentLocation.description[index],
      textAlign: TextAlign.left,
    ));
    if (index < widget.presentLocation.descriptionLinkUrls.length) {
      colChildWidget.add(Padding(
          padding: const EdgeInsets.symmetric(vertical: 16),
          child: Align(
              alignment: Alignment.center,
              child: index >=
                  widget.presentLocation.descriptionButtonIcons.length
                  ? ElevatedButton(
                  child: Text(
                      widget.presentLocation.descriptionButtonText[index]),
                  onPressed: () {
                    _launchURL(
                        widget.presentLocation.descriptionLinkUrls[index]);
                  })
                  : ElevatedButton(
                  child:
                  Row(mainAxisSize: MainAxisSize.min, children: [
                    Text(
                        widget.presentLocation.descriptionButtonText[index]),
                    SizedBox(width: 8),
                    FaIcon(
                        buttonIconMap[widget.presentLocation
                            .descriptionButtonIcons[index]],
                        size: 16)
                  ]),
                  onPressed: () {
                    _launchURL(
                        widget.presentLocation.descriptionLinkUrls[index]);
                  }))));
    }
    return Column(crossAxisAlignment: CrossAxisAlignment.start, children: colChildWidget);
  }

Should I regress from a builder to a conventional SliverList?

Other things I've tried: I eliminated jank in my app Drawer list by putting const everywhere possible, lots of Dividers were made Const. But when you style text using Theme.of(context).textTheme.bodyText2 etc. it doesn't allow you to set textboxes to const. If you want to use const you can't style the app globally, you'd have to hard code. Is it worth forsaking abstraction for hard coding Text widget styles?

Here is the web app: Love Edinburgh

For the clearest example of jank

  1. Open the App Drawer
  2. Scroll to WONDER
  3. Tap Arthur's Seat
  4. Open the panel to full screen - slide it up
  5. Scroll down the panel.

It doesn't show on a desktop browser which is compiled using Skia / Webkit. It's a bit fiddly to get scroll working on a desktop browser, you need to click amongst the text, then attempt to scroll. It's meant for mobile use so I'm resigned to that.

Sam
  • 1,659
  • 4
  • 23
  • 42

1 Answers1

1

Not sure how to help you out. Would rather put this on a comment rather than answer but I don't have the points to do a comment.

Anyway, I wish I could replicate your problem. On my personal experience, for a 15 item list with numerous child widgets, it shouldn't be janky unless it has probably big sized images or really too much extra widgets.

On my case, I made sure to "isolate" / "compute" my heavy computations and showed a loading screen while preparing the list.

You may read on:

Isolates : https://dart.dev/guides/language/concurrency

Compute: https://api.flutter.dev/flutter/foundation/compute-constant.html

Hope that helped!

mngkvn
  • 86
  • 7
  • Thanks for taking an interest. I've refactored to a `SliverChildBuilderDelegate` to get lazy loading, but this had increased my Widget tree, each item the builder gets served needs to be wrapped in a `Column` widget. Jank is still there, it may even be worse, the list being too short for the Builder's lazy loading efficiencies to kick in. I've tried putting `const` prefixes everywhere, but due to global app text styling that's limited. I've removed String formatting of the data in the panel's footer `ListTiles` to an async init process. I'm so surprised that web views may be superior. – Sam Dec 11 '22 at 12:06
  • Oh and I've edited my question to put up the jank creation code^ Very very happy to have it reviewed for performance improvement; I don't want to give up on native Flutter widgets for a Webview Widget. Webviews - that's not proper programming! – Sam Dec 11 '22 at 13:22
  • I've tested it on the browser and it's not janky. However, I see a little bit of jank on the mobile when going through the web address. Have you tried compiling it to apk or ios for release mode and see if there's still a jank? Also, have you tried to remove the emoji icons and see if that helps? I'm also seeing a jank on the drawer on the web.app version when I check it on the phone. – mngkvn Dec 12 '22 at 04:47
  • OK thanks for the feedback. I've refactored the Drawer's `ListView` parent & `ListView.builder` child. It's now a `CustomScrollView` with a `SliverFixedExtentList` child. I've lost some of the variable height list items I liked, but theoretically, the fixed height *should* minimise jank. When compiled to an app yes, it works fine, even with my prior nested Listview widgets. But the web app is likely to be used more, and I'm interested in learning best performance practice. I'm unwilling to forsake emojis - pictures with minimal load weight. Maybe this is the best the app can be – Sam Dec 12 '22 at 18:26
  • The app bar is smooth now. On the other note 1. Have you tried using web views? If you think that the app would be based for the web then you might want to give it a shot and at least test it and see if it makes more improvement. Not an expert but won't hurt. 2. Is it worth forsaking abstraction for hard coding Text widget styles? My opinion here is: do a custom global variable. As long as you use Media.queryOf + breakpoints. Easier to style and support different screen sizes. – mngkvn Dec 13 '22 at 04:29
  • Cheers for the continued feedback. This hobby app is a prototype for other potential works in future, so I think it's the quitting point for optimising. If/when I do something similar, if I use a pull-up panel I'll do a very simple native widget screen with little on it & the user will be invited to click through to a whole new screen for the greater body of text. That will be a Webview if it's going to be a long long body of text, or maybe native flutter widgets with sideways jumping screens - keep the content on each screen brief. Thanks for the encouragement to convert to sliverLists – Sam Dec 13 '22 at 22:40