Search code examples
flutterdartstatefulstatefulwidget

Flutter: FutureBuilder keeps flashing


I have an issue when calling setState().

Here is a screen recording of the issue: https://cln.sh/ndL24t.

As you can see, clicking on the widget causes the picture to flash from loading to picture.

The problem is caused because I have two futures and hence to future builders. The first future returns the information that is going to be displayed. The second future returns the images based on the id of the entry displayed on the dashboard. I have the first future builder get the text future. I set the future in initState() to avoid repeated calls. I then have another Future variable I set in initState() as follows journalFuture.then(...). I also have an AnimatedContainer() which animates when the user taps down or up/cancels by changing the values and calling setState(). I believe that is the cause of the issue but I don't know how to fix it.

Here is my code:

  const JournalSummary({Key? key}) : super(key: key);

  @override
  _JournalSummaryState createState() => _JournalSummaryState();
}

class _JournalSummaryState extends State<JournalSummary> {
  final GlobalKey lottieKey = GlobalKey(debugLabel: 'Lottie Key');

  late Future<List<JournalEntryData>> journalFuture;
  Future? picturesFuture;

  late Color _shadowColor;
  double _blurRadius = 15;

  void _animateDown() {
    setState(() {
      _shadowColor = Theme.of(context).shadowColor.withOpacity(0.40);
      _blurRadius = 25;
    });
  }

  void _animateUp() {
    setState(() {
      _shadowColor = Theme.of(context).shadowColor.withOpacity(0.19);
      _blurRadius = 15;
    });
  }

  @override
  void initState() {
    super.initState();
    journalFuture = DatabaseService(uid: AuthService().getUser()!.uid)
        .getJournalEntries(limit: 10);

    journalFuture.then((entries) {
      if (entries.isNotEmpty) {
        entries
            .sort((JournalEntryData firstEntry, JournalEntryData secondEntry) {
          DateTime firstDate = DateTime.parse(firstEntry.date);
          DateTime secondDate = DateTime.parse(secondEntry.date);

          int feelingCmp = secondEntry.feeling.compareTo(firstEntry.feeling);

          if (feelingCmp != 0) return feelingCmp;
          return secondDate.compareTo(firstDate);
        });
      }

      picturesFuture = StorageService(AuthService().getUser()!.uid)
          .getPictures(entries[0].date);
    });
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _shadowColor = Theme.of(context).shadowColor.withOpacity(0.19);
  }

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.transparent,
      child: FutureBuilder(
        future: journalFuture,
        builder: (context, entryFuture) {
          List<JournalEntryData> entries =
              (entryFuture.data as List<JournalEntryData>?) ?? [];

          if (entries.isNotEmpty) {
            entries.sort(
                (JournalEntryData firstEntry, JournalEntryData secondEntry) {
              DateTime firstDate = DateTime.parse(firstEntry.date);
              DateTime secondDate = DateTime.parse(secondEntry.date);

              int feelingCmp =
                  secondEntry.feeling.compareTo(firstEntry.feeling);

              if (feelingCmp != 0) return feelingCmp;
              return secondDate.compareTo(firstDate);
            });
          }

          return GestureDetector(
            onTap: () => AppTheme.homeNavkey.currentState!.pushReplacement(
              PageRouteBuilder(
                transitionDuration: Duration(milliseconds: 320),
                pageBuilder: (BuildContext context, Animation<double> animation,
                    Animation<double> secondaryAnimation) {
                  return Tasks(false);
                },
                transitionsBuilder: (BuildContext context,
                    Animation<double> animation,
                    Animation<double> secondaryAnimation,
                    Widget child) {
                  return Align(
                    child: FadeTransition(
                      opacity: animation,
                      child: child,
                    ),
                  );
                },
              ),
            ),
            onTapDown: (_) => _animateDown(),
            onTapUp: (_) => _animateUp(),
            onTapCancel: () => _animateUp(),
            child: AnimatedContainer(
              duration: const Duration(milliseconds: 200),
              margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 25),
              padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 10),
              clipBehavior: Clip.hardEdge,
              decoration: BoxDecoration(
                color: Theme.of(context).backgroundColor,
                borderRadius: BorderRadius.circular(15),
                boxShadow: [
                  BoxShadow(
                    color: _shadowColor,
                    blurRadius: _blurRadius,
                  ),
                ],
              ),
              child: Padding(
                padding: const EdgeInsets.symmetric(horizontal: 5),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      mainAxisSize: MainAxisSize.max,
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        SvgPicture.asset(
                          'assets/adobe/illustrator/icons/svg/journal_selected.svg',
                          width: 35,
                          height: 35,
                        ),
                        SizedBox(width: 8),
                        Text(
                          entryFuture.data == null
                              ? 'Journal'
                              : _formatedDate(entries[0].date),
                          maxLines: 1,
                          overflow: TextOverflow.ellipsis,
                          style: Theme.of(context)
                              .textTheme
                              .subtitle2!
                              .copyWith(color: Theme.of(context).primaryColor),
                        ),
                        Spacer(),
                        (entryFuture.data == null)
                            ? Container()
                            : Lottie.asset(
                                'assets/lottie/faces/${(entries[0].feeling == 1 ? 'sad' : (entries[0].feeling == 2 ? 'meh' : 'happy'))}.json',
                                key: lottieKey,
                                repeat: false,
                                width: 50,
                                height: 50,
                              ),
                      ],
                    ),
                    SizedBox(height: 10),
                    FutureBuilder(
                      key: UniqueKey(),
                      future: picturesFuture,
                      initialData: [],
                      builder: (context, picFuture) {
                        return (picFuture.connectionState ==
                                    ConnectionState.waiting ||
                                picturesFuture == null)
                            ? Center(child: CircularProgressIndicator())
                            : SizedBox(
                                width:
                                    MediaQuery.of(context).size.width - 40 - 50,
                                height: 160,
                                child: ListView(
                                  scrollDirection: Axis.horizontal,
                                  clipBehavior: Clip.none,
                                  children: _buildPictures(
                                      (picFuture.data as List<Uint8List>)),
                                ),
                              );
                      },
                    ),
                    SizedBox(height: 10),
                    (entryFuture.data == null)
                        ? Container()
                        : Text(
                            entries[0].entryText,
                            style: Theme.of(context).textTheme.bodyText2,
                            maxLines: 3,
                            overflow: TextOverflow.ellipsis,
                          ),
                  ],
                ),
              ),
            ),
          );
        },
      ),
    );
  }

  List<Widget> _buildPictures(List<Uint8List> pictures) {
    List<Widget> picWidgets = [];
    int index = 0;
    for (var pic in pictures) {
      picWidgets.add(
        Container(
          key: UniqueKey(),
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(10),
            boxShadow: [
              BoxShadow(
                blurRadius: 6,
                color: Theme.of(context).shadowColor.withOpacity(0.52),
                offset: Offset(0, 3),
              )
            ],
          ),
          clipBehavior: Clip.hardEdge,
          margin: index == 0
              ? EdgeInsets.only(right: 10)
              : EdgeInsets.symmetric(horizontal: 10),
          child: Image.memory(
            pic,
            key: UniqueKey(),
            height: 160,
            fit: BoxFit.cover,
          ),
        ),
      );
      index += 1;
    }
    return picWidgets;
  }

  String _formatedDate(String date) {
    DateTime dateTime = DateTime.parse(date);
    Map<int, String> _monthNumToName = {
      1: 'Jan',
      2: 'Feb',
      3: 'Mar',
      4: 'Apr',
      5: 'May',
      6: 'Jun',
      7: 'Jul',
      8: 'Aug',
      9: 'Sep',
      10: 'Oct',
      11: 'Nov',
      12: 'Dec',
    };

    return '${_monthNumToName[dateTime.month]} ${dateTime.day}, ${dateTime.year}';
  }
}


Solution

  • It worked. As mentioned in my comment, I ran flutter upgrade after the question was posted because a new version came out after posting the question, and also changed the code a little so I'm not sure what fixed the problem. It also could have been the fact that I stopped running then ran the app again.