nilssimon
nilssimon

Reputation: 21

How improve rendering performance in a flutter widget with many text widgets

we have a performance problem in our calendar widget. We want to display 3 Years in a scroll view. Each day should be displayed and some days should be marked. The problem is, that the rendering time of this widget is on some devises up to 5 seconds. I think the problem is that we need over 1000 Text widgets to display it. Does someone have an idea how to improve it?

I have written a small sample app. There are many simplifications in it, like every month has 31 days and the layout is bad, but it shows what we want and that it is too slow.

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool year = false;

  @override
  Widget build(BuildContext context) {
    const Key currentYearKey = Key('currentYearKey');
    return Scaffold(
        floatingActionButton: FloatingActionButton(
            onPressed: () => setState(() {
                  year = !year;
                })),
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: year
            ? CustomScrollView(
                center: currentYearKey,
                slivers: [
                  SliverToBoxAdapter(
                    child: Column(
                      children: [Year(), Year()],
                    ),
                  ),
                  SliverToBoxAdapter(
                    child: Year(),
                    key: currentYearKey,
                  )
                ],
              )
            : Text("1"));
  }
}

class Year extends StatelessWidget {
  const Year({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text("Year XX"),
          ...List.generate(
            4,
            (rowIndex) => Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisSize: MainAxisSize.min,
              children: List.generate(
                3,
                (columnIndex) {
                  return Expanded(
                      child: Month(
                    daySize: 14,
                    markedDays: [1, 3, 26, 30],
                  ));
                },
              ),
            ),
          ),
        ]);
  }
}

class Month extends StatelessWidget {
  const Month({required this.markedDays, required this.daySize, Key? key})
      : super(key: key);
  final List<int> markedDays;
  final double daySize;

  @override
  Widget build(BuildContext context) {
    return GridView.count(
        padding: EdgeInsets.zero,
        crossAxisCount: 7,
        shrinkWrap: true,
        physics: const NeverScrollableScrollPhysics(),
        children: List.generate(31, (index) {
          final bool isMarked = markedDays.contains(index);
          return Center(
            child: Container(
              height: daySize,
              width: daySize,
              decoration:
                  isMarked ? BoxDecoration(color: Colors.lightBlue) : null,
              child: Text(
                "$index",
                style: isMarked ? TextStyle(fontWeight: FontWeight.bold) : null,
              ),
            ),
          );
        }));
  }
}

screenshot

We tried to make as much as possible const, it improved it abit, and make it around 30% faster, but we need it much faster. We also tried to replace the GridView in the month through a table or row/column construct, but it does not help.

Upvotes: 1

Views: 1308

Answers (4)

Fola Oluwafemi
Fola Oluwafemi

Reputation: 31

As of right now, with every SliverToBoxAdapter -> Column group, flutter tries to lazily render, but each item in the CustomScrollview is too large rendering ends up takes a lot of time.

I suggest you use Multisliver from package sliver_tools instead of SliverToBoxAdapter -> Column as you are doing right now...

..essentially try to turn as many "Box" multichild widget into a Sliver widget, as you can. This would make it such that flutter will now render them lazily.

Column -> Column -> ... -> Column would turn into Multisliver -> ... (you get the gist).

and finally, to avoid shrinkWrapping your GridView, maybe use Wrap widget instead?

I hope this helps you or someone else who comes across this. :)

edit: for cases where you need to decorate a multichild widget (add background color, shadow, etc), use SliverStack and wrap your decorating widget with a SliverPositioned.filled widget, all these are from package sliver_tools. cheers 🥂

Upvotes: 0

nilssimon
nilssimon

Reputation: 21

The solution was to compile it in release mode. It does still need some time to render, but much less. Thanks @rszf for the help.

For those who have the same problem, and don't know the build modes, here is some information: https://docs.flutter.dev/testing/build-modes

Upvotes: 1

Bermjly Team
Bermjly Team

Reputation: 3671

In this case you defiantly must use GridView.builder / ListView.builder.

This will lead to build widgets on demand. Instead of building everything at once which is Bad practice.

Upvotes: 0

rszf
rszf

Reputation: 201

Instead of columns use ListView.builder, this helped me with long lists: https://docs.flutter.dev/cookbook/lists/long-lists

Upvotes: 1

Related Questions