1

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

selected
  • 764
  • 2
  • 10
  • 19

1 Answers1

1

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

Community
  • 1
  • 1
selected
  • 764
  • 2
  • 10
  • 19
  • 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. – Randal Schwartz Oct 15 '19 at 19:56