import "package:flutter/material.dart";
import "dart:async";

class JoinScreen extends StatefulWidget {
   @override
  _JoinScreenState createState() {   
   return _JoinScreenState();
          }
   }

class _JoinScreenState extends State<JoinScreen> {
  List<Widget> widgetList = [];

  @override
  void initState() {
    new Timer(const Duration(milliseconds: 100), () {
     print('timeout');
    setState(() {        
       widgetList.add(secondHalf());
     });
    });

new Timer(const Duration(milliseconds: 1000), () {
  print('timeout');
  setState(() {        
    widgetList.add(firstHalf());
  });
});

super.initState();
 }

 @override
 Widget build(BuildContext context) {
   return AnimatedContainer(
     duration: Duration(seconds: 2),
      child: Column(
       children: widgetList,
     ),
   );
 }

 Widget firstHalf() {
   return Expanded(
     child: Container(
       decoration: BoxDecoration(color: Colors.blueAccent),
     ),
   );
 }

 Widget secondHalf() {
   return Expanded(
     child: Container(
       decoration: BoxDecoration(color: Colors.pinkAccent),
     ),
     );
    }
}

If i changed the width and height of the container with the help of timer and setstate, it animates. But when adding two new list of widgets to the build function, nothing animates.

I want to have a expanding animation. Because i am using expanded, i am not able to give specific height which is meaningless with expanded.

How do i do this?


Solution 1: rmtmckenzie

This is almost certainly a duplicate as I think I've answered this before, but I can't find the question easily so I'll just answer it again.

To understand why this isn't working, first you have to understand how Dart does comparisons on objects. If an object is a primitive, simple, or has comparison functions/operators defined (i.e. int, boolean, String, etc) dart can compare the objects. If an object is more complicated and doesn't define compareTo, operator=, operator< or operator>, dart doesn't know how to do that type of comparison on it. Instead, a comparison turns into "is object a the same object as object b".

This is important because Flutter is lazy. It doesn't want to have to rebuild widgets unless it absolutely has to. So when you change the state with setState, flutter then comes along and takes a look at your State to see if it has actually changed. With the cases of height or width it's easy - it can check that those have changed.

The reason it doesn't work when you mutate your list is exactly that; you're mutating an existing list. So when dart checks whether oldState.widgetList == newState.widgetList, it's not actually comparing whether each element of the list is the same but rather checking whether the list is the same. Since it's the same object, the list shows as being the same so flutter moves on to the next step without rebuilding.

There are three main ways to get around this. The first is to make a copy of the list each time you edit it. Depending on how many elements are in the list, this could be a bad idea - when you copy the elements you're not actually copying each little bit of information, but it still is an O(n) operation.

The second is to maintain a separate variable in the State. The reason this helps is because if any part of the state has changed, it triggers a rebuild of all the widgets i.e. calls the build function, whether or not the actual property that each widget uses is 'changed' (mostly because it would be very difficult to manage keeping track of that for each and every widget that is built). I personally do it this way - I maintain an integer counter that I just increment each time a change happens in the list. It might not be the cleanest solution, but it is performant and pretty simple!

The last way would be to implement your own list that does do a 'deep' comparison (i.e. checks whether number of elements are the same, and then possibly checks whether each element is the same). This isn't done by default in dart's lists because it would be easy to cause performance issues if you start comparing strings without realizing each element in the list could be used as part of the comparison.


Solution 2: Arjunraj kokkadan

Use AnimatedSize instead of AnimatedContainer.

for AnimatedSize to work we need to use a mixin called SingleTickerProviderStateMixin on the _JoinScreenState and set the vsync property to the current instance (this) after that the AnimatedSize will look for changes on its child and animate accordingly,

Update: TickerProvider vsync is deprecated after Flutter v2.2.0-10.1.pre. It is now implemented in the widget (AnimatedSize) itself. for reference check the source code.

here is your code

import "package:flutter/material.dart";
import "dart:async";

class JoinScreen extends StatefulWidget {
@override
 _JoinScreenState createState() {   
    return _JoinScreenState();
    }
 }

class _JoinScreenState extends State<JoinScreen> 
 with SingleTickerProviderStateMixin {
  List<Widget> widgetList = [];

  @override
  void initState() {
    new Timer(const Duration(milliseconds: 100), () {
    print('timeout');
    setState(() {        
    widgetList.add(secondHalf());
   });
  });

  new Timer(const Duration(milliseconds: 1000), () {
    print('timeout');
    setState(() {        
      widgetList.add(firstHalf());
    });
  });

  super.initState();
 }

 @override
 Widget build(BuildContext context) {
   return AnimatedSize(
     vsync: this,
     duration: Duration(seconds: 2),
     child: Column(
       children: widgetList,
       ),
   );
 }

 Widget firstHalf() {
   return Expanded(
     child: Container(
       decoration: BoxDecoration(color: Colors.blueAccent),
     ),
   );
 }

 Widget secondHalf() {
   return Expanded(
     child: Container(
       decoration: BoxDecoration(color: Colors.pinkAccent),
     ),
    );
  }
}