Flutter Long list poor performance

5,784

I think using ListView.builder might help you as it will render only the visible items.

Here is an article about widget optimization which explains the differences in performance when building some widgets.

Share:
5,784
relascope
Author by

relascope

Studied Software Engineering in 2003 and worked for Companies developing Software in Dart, Java, C++, C#.Net, VB.net, PHP in the fields of Office Automation, GPS on Mobile Devices, Medical Systems, CAD for AutoCAD, automatic generation of CAD from calculations on a ropeway system, Time Management integrated with ERP system, Supply Chain Management, Business Process Management, Music Informatics...

Updated on December 22, 2022

Comments

  • relascope
    relascope over 1 year

    I tried using Listview and Sliverlist, but the performance of my list is very slow. Even if the Items are loaded from the internet, the scrolling is very slow.

    Anybody some thoughts about the slow code?

    import 'dart:typed_data';
    import 'dart:math';
    
    import 'package:flutter/material.dart';
    import 'package:flutter/rendering.dart';
    import 'package:flutter/widgets.dart';
    import 'package:flutter_bloc/flutter_bloc.dart';
    import 'package:font_awesome_flutter/font_awesome_flutter.dart';
    import 'package:impex_shop/bloc/warenkorb_bloc.dart';
    import 'package:impex_shop/data/articlepodo.dart';
    import 'package:impex_shop/routenames.dart';
    import 'package:impex_shop/services/repository.dart';
    import 'package:impex_shop/styles/impex_icons.dart';
    import 'package:impex_shop/styles/impex_styles.dart';
    import 'package:impex_shop/utils/utils.dart';
    import 'package:impex_shop/widgets/myfuturebuilder.dart';
    
    class SearchResultsWidget extends StatefulWidget {
      final Map<String, String> search;
    
      SearchResultsWidget(this.search);
    
      @override
      _SearchResultsWidgetState createState() =>
          _SearchResultsWidgetState(this.search);
    }
    
    class _SearchResultsWidgetState extends State<SearchResultsWidget> {
      Map<String, String> search;
      Future<ArticleIdList> _articleIds;
    
      _SearchResultsWidgetState(this.search);
    
      @override
      void initState() {
        super.initState();
        _articleIds = Repository().queryArticleSearch(search);
      }
    
      @override
      Widget build(BuildContext context) {
        return FutureBuilder(
          future: _articleIds,
          //showProgressIndicator: true,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.done &&
                snapshot.hasData) {
              ArticleIdList articleIds = snapshot.data;
              if (articleIds.articleIds.length == 0) {
                return Container(
                  padding: EdgeInsets.symmetric(
                      horizontal: ImpexStyle.horizontalPadding),
                  child: Text('Kein Artikel gefunden'),
                );
              }
              return CustomScrollView(
                slivers: <Widget>[
                  SliverList(
                    delegate: SliverChildBuilderDelegate(
                      (BuildContext context, int index) {
                        if (index == 0) {
                          return Container(
                            height: ImpexStyle.verticalPadding,
                          );
                        }
                        Future<Article> article = Repository()
                            .queryArticleDetails(articleIds.articleIds[index - 1]);
                        return MyFutureBuilder(
                          future: article,
                          builder: (context, article) {
                            return SearchResultLineWidget(article: article);
                          },
                        );
                      },
                      childCount: articleIds.articleIds.length + 1,
                    ),
                  )
                ],
              );
            } else if (snapshot.hasError) {
              return Row(
                children: <Widget>[
                  Icon(
                    Icons.error,
                    color: ImpexColors.errorColor,
                    size: 30,
                  ),
                  Expanded(
                    child: Text(
                      '${snapshot.error}',
                      style: TextStyle(color: ImpexColors.errorColor),
                    ),
                  )
                ],
              );
            } else {
              return Center(
                child: CircularProgressIndicator(),
              );
            }
          },
        );
      }
    }
    
    class SearchResultLineWidget extends StatefulWidget {
      const SearchResultLineWidget({this.article});
    
      final Article article;
    
      @override
      State<StatefulWidget> createState() {
        return SearchResultLineState(article);
      }
    }
    
    class SearchResultLineState extends State<SearchResultLineWidget> {
      SearchResultLineState(this._article);
    
      final Article _article;
      Future<String> _articleImageData;
    
      @override
      void initState() {
        super.initState();
        _articleImageData = Repository().queryArticleImage(
          _article.id,
          width: ImpexStyle.imageSize,
          height: ImpexStyle.imageSize,
        );
      }
    
      @override
      Widget build(BuildContext context) {
        WarenkorbBloc warenkorbBloc = BlocProvider.of<WarenkorbBloc>(context);
        bool isInWarenkorb = (warenkorbBloc.currentState as WarenkorbLoaded)
                .warenkorbLines[_article.id] !=
            null;
    
        return Column(
          children: <Widget>[
            FlatButton(
              onPressed: (() {
                Navigator.pushNamed(context, RouteName.ARTICLE_DETAIL,
                    arguments: _article.id);
              }),
              child: Row(
                children: <Widget>[
                  FutureBuilder<String>(
                    future: _articleImageData,
                    builder: (context, snapshot) {
                      if (snapshot.hasData &&
                          snapshot.connectionState == ConnectionState.done) {
                        var imageData = snapshot.data;
                        if (imageData.isEmpty || imageData == 'null')
                          return Container(
                            width: ImpexStyle.imageSize.toDouble(),
                            height: ImpexStyle.imageSize.toDouble(),
                            child: EmptyImageIcon(),
                          );
                        return Container(
                          height: ImpexStyle.imageSize.toDouble(),
                          width: ImpexStyle.imageSize.toDouble(),
                          child: Stack(children: <Widget>[
                            Image(
                              image: MemoryImage(
                                Uint8List.fromList(imageData.codeUnits),
                              ),
                            ),
                            isInWarenkorb
                                ? Icon(FontAwesomeIcons.shoppingCart)
                                : Center(),
                          ]),
                        );
                      } else if (snapshot.hasError) {
                        return Container(
                          width: ImpexStyle.imageSize.toDouble(),
                          height: ImpexStyle.imageSize.toDouble(),
                          child: Text(snapshot.error),
                        );
                      }
    
                      return Container(
                        width: ImpexStyle.imageSize.toDouble(),
                        height: max(96, ImpexStyle.imageSize.toDouble()),
                        child: Center(),
                      );
                    },
                  ),
                  Container(width: ImpexStyle.horizontalPadding),
                  Flexible(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: <Widget>[
                        Row(
                          children: <Widget>[
                            Expanded(
                              child: Text(
                                _article.no,
                                style: ImpexStyle.fontStyleSmall,
                              ),
                            ),
                            Spacer(),
                            PriceLagerWidget(_article),
                          ],
                        ),
                        Text(_article.name, style: ImpexStyle.fontStyleNormal)
                      ],
                    ),
                  )
                ],
              ),
            ),
            Divider(color: ImpexColors.dividerColor),
          ],
        );
      }
    }
    
    class PriceLagerWidget extends StatelessWidget {
      PriceLagerWidget(this._article)
          : _articlePrice = Repository().queryArticlePrice(_article.id);
    
      final Article _article;
      final Future<ArticlePrice> _articlePrice;
    
      @override
      Widget build(BuildContext context) {
        return MyFutureBuilder<ArticlePrice>(
          future: _articlePrice,
          builder: (context, articlePrice) {
            String priceString = getPriceString(articlePrice, context);
            return Row(
              children: <Widget>[
                Row(
                  children: <Widget>[
                    _article.isRaffleWin
                        ? RaffleWinIcon()
                        : Text('€', style: ImpexStyle.fontStyleSmall),
                    Text(' $priceString ', style: ImpexStyle.fontStyleSmall)
                  ],
                ),
                AvailabilityIcon(this._article.availabilityColor)
              ],
            );
          },
        );
      }
    }