Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to smoothly update a Flutter AnimatedList without using a GlobalKey.currentState?

All examples I have found of inserting items into a Flutter AnimatedList utilize a GlobalKey.currentState to tell the AnimatedList widget that an item has been inserted. Same thing with delete from the list.

It is known that in Flutter a GlobalKey's currentState may be null.

In my case, I am waiting on a Firebase message to come through. When it appears that message is inserted into the AnimatedList's List data and if one were to follow the prevalent examples shown for AnimatedList one would use the assigned GlobalKey.currentState to tell the AnimatedList where the new item was inserted.

But, in my case when I attempt to use it, the GlobalKey.currentState is null.

What can I use otherwise? I know I can use setState but that causes a full widget refresh which appears in this case to be a flash on the screen rather than the desired AnimatedList insert which is nice and smooth.

Many people have eluded to using an InheritedWidget but I cannot understand how it would be implemented in this case if that is the correct technique.

/////////
// NOTE:  This is close to real code, but consider it pseudocode since it is a cut and paste from the real code less quite a bit of extra  stuff I have in there.  It certainly will not compile but it is representative of the issue.
///////// 

class Conversation extends StatefulWidget {
  Conversation({Key key, @required this.cid, this.notificationsHub}) : super(key: key);

  // A bit of a hack here, but my Firebase cloud messages come through this object
  MyNotificationsHub notificationsHub;

  // a conversation id 
  final int cid;

  @override
  ConversationState createState() => ConversationState(cid: this.cid, notificationsHub: notificationsHub);
}

//////////

class ConversationState extends State<Conversation> 
    with MyNotificationsListener {
       ConversationState({Key key, @required this.cid, this.notificationsHub}); // : super(key: key);

MyNotificationsHub notificationsHub;

List<ConversationFeed> feed = List();

// The GlobalKey keeps track of the visible state of the list items
// while they are being animated.
final GlobalKey<AnimatedListState> _listKey = GlobalKey();

///////////

initState() {
    notificationsHub.addNotificationsListener(this);
}

///////////
// This is where the issue occurs, when a Firebase message comes in and I attempt to use the GlobalKey the currentState component is null
///////////
void receiveAlert ( MyNotificationItem alert ){
  int insertIndex=0;

  feed.insert(insertIndex, new ConversationFeed( alert ) );

  //******* Issue below, null currentState *******
  _listKey.currentState.insertItem(insertIndex);
  //******* Issue above, null currentState *******
}

////////////

 @override
  Widget build(BuildContext context) {
     return Scaffold(
        appBar: AppBar(
          title: Text("Conversation")),
        body: 

          new AnimatedList(

            // Give the Animated list the global key
            key: _listKey,

            initialItemCount: feed.length,

            itemBuilder: (context, index, animation) {
            return ListTile(
                title: Text ( feed[index].getMessageContentsString() );
          })
       );
}

Actual results are a null pointer error being thrown during running due to the GlobalKey's currentState being null.

/// Updates:

I tried the AnimatedList.of(context) method along with a Builder to solve this problem. I tried it in the following manner:

// Declared an instance variable of BuildContext in my class definition

BuildContext acontext;

// in the Builder call I assigned it in the following manner:

body: Builder(
            builder: (BuildContext zcontext) { 
              acontext = zcontext ;
              return ( AnimatedList (....) ); }
            )

// & inside my receiveAlert method I used it in the following manner:

void receiveAlert ( MyNotificationItem alert ){
 ....
 AnimatedList.of(acontext).insertItem(insertIndex);
}

When using the above constructs I get the following error:

AnimatedList.of() called with a context that does not contain an AnimatedList.

I'm sure the Flutter team has provided a manner to trigger an update external to the Widget tree, I'm just not sure how to get to it. Perhaps my techniques are incorrect.

like image 448
E.Bradford Avatar asked Jul 05 '19 05:07

E.Bradford


1 Answers

I've had this same issue when animating the introduction of a new item in an animated list widget. As strange as it may seem you can solve this problem by checking if the state is still null:

if(animatedListKey.currentState != null)
  animatedListKey.currentState.insertItem(0);

This means that it won't try to insert the item, while rebuilding, until the state is available. Try it.

You can check the code from my tutorial here: https://github.com/Morthor/flutter_todo_app_talk/blob/youtube_animatedList/lib/main.dart

like image 124
João Soares Avatar answered Sep 27 '22 16:09

João Soares