Flutter: How to re-render the view without making an API call?

868

Here is how I solved my problem, but firstly I'll try to explain what was the cause.

The problem

The problem was that I had my getData() method that is calling the API in my build method, therefore, every time you'd make a change it calls the build method which then executes the getData method respectively.

...
future: getData()
...

The fix

The getData() method must be executed in the initState() to prevent it from executing multiple times. First, I defined a variable dataFetched which will only be set when the getData() method finishes. Once the it is set, the future builder will continue executing and run the code within it.

...
var dataFetched;

@override
  void initState(){
    super.initState();
    dataFetched = getData();
  }

...
FutureBuilder(
  future: dataFetched,
     builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
        if(snapshot.connectionState == ConnectionState.done){...}
     }
  }
)               
...

I am not sure that this is the best way of doing it, but at least it fixed my problem.

Please read this thread for more detailed explanation.

How to deal with unwanted widget build?

Credits: Rémi Rousselet

Share:
868
selected
Author by

selected

Updated on December 14, 2022

Comments

  • selected
    selected over 1 year

    So I have this code that fetches routes from one place to another. Please see the picture attached below.

    I need to add details for each journey which will be hidden unless you click on 'Details' button, I want to show the data in a ExpansionTile/ExpansionPanel - basically a collapsing element.

    The problem is that when I use the setState() {...} on the button, it will show the data indeed but it will also make the API call again.

    The method getData() gets triggered in the build widget which is why (I suppose) this is happening but I don't know how to solve this.

    Is there a better way of doing it?

    Any help/improvements will be highly appreciated as I am new to Flutter =)

    ...
    class _PlanJourney extends State<PlanJourney> {
      String data;
      final from;
      final to;
      final type;
      final time;
      static var date = new DateTime.now();
      final String formattedDate = new DateFormat('yyyy-MM-dd').format(date);
    
      double _animatedHeight = 100.0;
    
      Map<String,dynamic> journeyData;
    
      _PlanJourney(this.from, this.to, this.type, this.time);
    
      Future<String> getData() async {
    
        http.Response response = await http.get(url);
        data = response.body;
        journeyData = json.decode(data);
        debugPrint(journeyData.toString());
      }
    
      @override
      Widget build(BuildContext context){
        return Scaffold(
          appBar: AppBar(
            title: Text('Plan a journey'.toUpperCase(),
              style: TextStyle(
                color: Colors.black,
                fontSize: 20,
                fontWeight: FontWeight.bold
              )
            ),
            iconTheme: IconThemeData(
              color: Color(0xFF0984e3), 
            ),
            backgroundColor: Colors.white,
          ),
          resizeToAvoidBottomPadding: false,
          body: SingleChildScrollView(
            child: ConstrainedBox(
              constraints: BoxConstraints(),
              child: FutureBuilder(
                future: getData(),
                builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
                  if(snapshot.connectionState == ConnectionState.done){
                    List<Widget> elements = new List<Widget>();
                    for(var i = 0; i < journeyData['routes'].length; i++){
                      List<Widget> iconList = new List<Widget>();
                      String duration;
                      String start = journeyData['routes'][i]['departure_time'];
                      String end;
                      String stress = '2 min';
                      for(var j = 0; j < journeyData['routes'][i]['route_parts'].length; j++){
                        if(journeyData['routes'][i]['route_parts'][j]['mode'] == 'foot'){
                          iconList.add(IconTheme(
                              data: IconThemeData(
                                color: Color(0xFFbfcdd5)
                              ),
                              child: Icon(Icons.directions_walk),
                            )
                          );
                        } else if(journeyData['routes'][i]['route_parts'][j]['mode'] == 'tube'){
                            iconList.add(IconTheme(
                                data: IconThemeData(
                                  color: Color(0xFFbfcdd5)
                                ),
                                child: Icon(Icons.train),
                              )
                            );
                        } else if(journeyData['routes'][i]['route_parts'][j]['mode'] == 'bus'){
                            iconList.add(IconTheme(
                                data: IconThemeData(
                                  color: Color(0xFFbfcdd5)
                                ),
                                child: Icon(Icons.directions_bus),
                              )
                            );
                        }
                        duration = journeyData['routes'][i]['duration'];
                        end = journeyData['routes'][i]['arrival_time'];
                      }
                      elements.add(Container(
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.stretch,
                          children: <Widget>[
                            Container(
                              alignment: Alignment.topLeft,
                              margin: EdgeInsets.only(left: 15, right: 15),
                              padding: EdgeInsets.all(10),
                              child: new Wrap(
                                direction: Axis.horizontal,
                                crossAxisAlignment: WrapCrossAlignment.start,
                                spacing: 5,
                                runSpacing: 5,
                                children: iconList
                              ),
                            ),
                            Container(
                              alignment: Alignment.topLeft,
                              margin: EdgeInsets.only(left: 15, right: 15),
                              padding: EdgeInsets.all(10),
                              child: new Row(
                                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                                children: <Widget> [
                                  Column(
                                    children: <Widget>[
                                      Text('Duration',
                                        style: TextStyle(
                                          fontFamily: "Gotham Pro",
                                          fontWeight: FontWeight.w300,
                                          fontSize: 14
                                        )
                                      ),
                                      Padding(
                                        padding: EdgeInsets.only(top: 5),
                                      ),
                                      Text(duration)
                                    ],
                                  ),
                                  Column(
                                    children: <Widget>[
                                      Text('Start',
                                        style: TextStyle(
                                          fontFamily: "Gotham Pro",
                                          fontWeight: FontWeight.w300,
                                          fontSize: 14
                                        )
                                      ),
                                      Padding(
                                        padding: EdgeInsets.only(top: 5),
                                      ),
                                      Text(start),
                                    ],
                                  ),
                                  Column(
                                    children: <Widget>[
                                      Text('End',
                                        style: TextStyle(
                                          fontFamily: "Gotham Pro",
                                          fontWeight: FontWeight.w300,
                                          fontSize: 14
                                        )
                                      ),
                                      Padding(
                                        padding: EdgeInsets.only(top: 5),
                                      ),
                                      Text(end),
                                    ],
                                  ),
                                  Column(
                                    children: <Widget>[
                                      Text('Stress',
                                        style: TextStyle(
                                          fontFamily: "Gotham Pro",
                                          fontWeight: FontWeight.w300,
                                          fontSize: 14
                                        )
                                      ),
                                      Padding(
                                        padding: EdgeInsets.only(top: 5),
                                      ),
                                      Text(stress)
                                    ],
                                  )
                                ]
                              ),
                            ),
                            Container(
                              alignment: Alignment.topLeft,
                              margin: EdgeInsets.only(left: 15, right: 15),
                              child: Row(
                                children: <Widget>[
                                  OutlineButton(
    
                                    child: Text('Details', 
                                      style: TextStyle(
                                        color: Color(0xFF0c85e4), 
                                        fontFamily: "Gotham Pro",
                                        fontWeight: FontWeight.w700
                                      )
                                    ),
                                    borderSide: BorderSide(
                                      color: Color(0xFF0c85e4), //Color of the border
                                      style: BorderStyle.solid, //Style of the border
                                      width: 2, //width of the border
                                    ),
                                    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
                                    onPressed: ()=>setState((){
                                        _animatedHeight!=0.0  _animatedHeight=0.0:_animatedHeight=100.0;
                                      }
                                    ),
                                  ),
                                  Padding(
                                    padding: EdgeInsets.only(left: 15, top: 15),
                                  ),
                                  RaisedButton(
                                    child: Text('Save', 
                                      style: TextStyle(
                                        color: Colors.white,
                                        fontFamily: "Gotham Pro",
                                        fontWeight: FontWeight.w700,
                                      )
                                    ),
                                    color: Color(0xFF08b894),
                                    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
                                    onPressed: (){},
                                  )
                                ],
                              )
                            ),
                            AnimatedContainer(duration: const Duration(milliseconds: 120),
                              child: new Text("Journey data will go here"),
                              height: _animatedHeight,
                              color: Colors.tealAccent,
                              width: 200.0,
                            ),
                            Divider()
                          ],
                        ),
                      ));
                    }
                    return new Column(children: elements);
                  }else if(snapshot.connectionState == ConnectionState.waiting){
                    return Text("loading ...");
                  }
                },
              ),
            )
          )
        );
      }
    
    }
    

    enter image description here

    enter image description here

  • Randal Schwartz
    Randal Schwartz over 4 years
    You should act as if your build() method might be called 60 times per second, so it should be fast and idempotent (no side effects like API calls). In practice, the framework optimizes most of those away.