2

Whenever I change the page and come back, the api is reloaded. I have tried many suggestions. I would be glad if you help.

Here are the methods I tried : How to avoid reloading data every time navigating to page

How to parse JSON only once in Flutter

Flutter Switching to Tab Reloads Widgets and runs FutureBuilder

import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

import 'models/Word.dart';

class WordListPage extends StatefulWidget {
  WordListPage(Key k) : super(key: k);

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

class _WordListPageState extends State<WordListPage> {
  Future<List<Word>> data;
  bool isSearching = false;
  TextEditingController myController = TextEditingController();
  List<Word> _words = List<Word>();
  List<Word> _wordsForDisplay = List<Word>();
  var keyListPage = PageStorageKey('list_page_key');

  Future<List<Word>> getWord() async {
    var response = await http.get("myAPIurl");
    var _words = List<Word>();

    _words = (json.decode(utf8.decode(response.bodyBytes)) as List)
        .map((singleWordMap) => Word.fromJsonMap(singleWordMap))
        .toList();
    return _words;
  }

  @override
  void initState() {
    getWord().then((value) {
      setState(() {
        _words.addAll(value);
        _wordsForDisplay = _words;
      });
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: myFutureBuilder(),
      appBar: AppBar(
        leading: Center(
          child: RichText(
            textAlign: TextAlign.center,
            text: TextSpan(
              style: DefaultTextStyle.of(context).style,
              children: <TextSpan>[
                TextSpan(
                  text: 'Total',
                  style: TextStyle(
                    decoration: TextDecoration.none,
                    fontSize: 10,
                    color: Colors.white,
                  ),
                ),
                TextSpan(
                  text: '\n${_words.length.toString()}',
                  style: TextStyle(
                    decoration: TextDecoration.none,
                    fontWeight: FontWeight.bold,
                    color: Colors.white,
                    fontSize: 12,
                  ),
                ),
                TextSpan(
                  text: '\nLetter',
                  style: TextStyle(
                    decoration: TextDecoration.none,
                    color: Colors.white,
                    fontSize: 10,
                  ),
                ),
              ],
            ),
          ),
        ),
        centerTitle: true,
        title: !isSearching
            ? Text('My Title')
            : TextField(
                autofocus: true,
                style: TextStyle(color: Colors.white),
                controller: myController,
                onChanged: (value) {
                  value = value.toLowerCase();
                  setState(
                    () {
                      _wordsForDisplay = _words.where(
                        (word) {
                          var wordTitle = word.word.toLowerCase();
                          return wordTitle.contains(value);
                        },
                      ).toList();
                    },
                  );
                  setState(
                    () {
                      _wordsForDisplay = _words.where(
                        (word) {
                          var wordPronounce = word.pronunciation.toLowerCase();
                          return wordPronounce.contains(value);
                        },
                      ).toList();
                    },
                  );
                },
                decoration: InputDecoration(
                  isCollapsed: true,
                  icon: Icon(
                    Icons.menu_book,
                    color: Colors.white,
                  ),
                  hintText: 'Search',
                  hintStyle: TextStyle(color: Colors.white),
                ),
              ),
        actions: [
          isSearching
              ? IconButton(
                  icon: Icon(Icons.cancel_outlined),
                  onPressed: () {
                    setState(
                      () {
                        this.isSearching = false;
                        myController.clear();
                        _wordsForDisplay = _words.where(
                          (word) {
                            var wordTitle = word.word.toLowerCase();
                            return wordTitle.contains(wordTitle);
                          },
                        ).toList();
                      },
                    );
                  },
                )
              : IconButton(
                  icon: Icon(Icons.search_sharp),
                  onPressed: () {
                    setState(
                      () {
                        this.isSearching = true;
                      },
                    );
                  },
                ),
        ],
      ),
    );
  }

  FutureBuilder<List<Word>> myFutureBuilder() {
    return FutureBuilder(
      future: getWord(),
      builder: (context, AsyncSnapshot<List<Word>> snapshot) {
        if (snapshot.hasData) {
          return myWordListView(snapshot);
        } else {
          return Center(
            child: CircularProgressIndicator(),
          );
        }
      },
    );
  }

  ListView myWordListView(AsyncSnapshot<List<Word>> snapshot) {
    return ListView.builder(
      itemCount: _wordsForDisplay.length,
      itemBuilder: (context, index) {
        return ExpansionTile(
          title: Text(
            _wordsForDisplay[index].word,
            style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16.0),
          ),
          subtitle: Text(
            snapshot.data[index].pronunciation[0].toUpperCase() +
                snapshot.data[index].pronunciation.substring(1),
          ),
          leading: CircleAvatar(
            child: Text(snapshot.data[index].word[0]),
          ),
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Container(
                  width: MediaQuery.of(context).size.width,
                  child: Padding(
                    padding: const EdgeInsets.symmetric(
                        vertical: 7.0, horizontal: 19.0),
                    child: RichText(
                      text: TextSpan(
                        style: DefaultTextStyle.of(context).style,
                        children: <TextSpan>[
                          TextSpan(
                            text: snapshot.data[index].word + ' : ',
                            style: TextStyle(fontWeight: FontWeight.bold),
                          ),
                          TextSpan(text: snapshot.data[index].meaning),
                        ],
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ],
        );
      },
    );
  }
}
OneMore
  • 89
  • 1
  • 7
  • The reason your API reload whenever you come back to this page is becasue `initState()` get called. This means whatever function inside `initState()` will get called. This happens to your getWord() function. – HeIsDying Mar 04 '21 at 11:06
  • But how can I do it without defining it in initState? – OneMore Mar 04 '21 at 11:24

3 Answers3

0

When you navigate and return back, the build method is called. Here you have "myFutureBuilder" placed as the body widget of Scaffold, thus this code get executed, within it "getWord" method is called, an it fetches data from the api everytime.

I suggest you to remove "myFutureBuider" and use "myWirdListView" directly as the body of the scaffold. Change myWordListView(List<Word> listOfWord) to use the word list you have already fetched in the initState() .

nico
  • 63
  • 1
  • 1
  • 6
0

You need to to separate your api call from your ui. That way your api will only get called when you want it to. I recommend using some kind of external state management library, such as Provider, BLoC, or RxDart. Flutter will rebuild a widget anytime it wants to, beyond when you trigger it.

Scott Godfrey
  • 641
  • 5
  • 8
  • I will research for BLoC thank you but I need a faster solution – OneMore Mar 04 '21 at 13:02
  • You need to take the call out of initState(). If you need the data on first load, you can call it from the same function you use to navigate to the WordListPage. Just make sure you await it. – Scott Godfrey Mar 04 '21 at 13:33
0

you passed getWord() as a function to you future builder so each time the widget get rebuilt, it is called.

To solve that, declare a variable Future<Word> getWordFuture ; , Inside initState assign getWordFuture = getWord(); and use getWordFuture in the Future builder.


import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

import 'models/Word.dart';

class WordListPage extends StatefulWidget {
  WordListPage(Key k) : super(key: k);

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

class _WordListPageState extends State<WordListPage> {
  Future<List<Word>> data;
  bool isSearching = false;
  TextEditingController myController = TextEditingController();
  List<Word> _words = List<Word>();
  List<Word> _wordsForDisplay = List<Word>();
  var keyListPage = PageStorageKey('list_page_key');
Future<List<Word>> getWordFuture = Future<List<Word>> ; //1

  Future<List<Word>> getWord() async {
    var response = await http.get("myAPIurl");
    var _words = List<Word>();

    _words = (json.decode(utf8.decode(response.bodyBytes)) as List)
        .map((singleWordMap) => Word.fromJsonMap(singleWordMap))
        .toList();
    return _words;
  }

  @override
  void initState() {
   getWordFuture = getWord(); //2
    getWord().then((value) {
      setState(() {
        _words.addAll(value);
        _wordsForDisplay = _words;
      });
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: myFutureBuilder(),
      appBar: AppBar(
        leading: Center(
          child: RichText(
            textAlign: TextAlign.center,
            text: TextSpan(
              style: DefaultTextStyle.of(context).style,
              children: <TextSpan>[
                TextSpan(
                  text: 'Total',
                  style: TextStyle(
                    decoration: TextDecoration.none,
                    fontSize: 10,
                    color: Colors.white,
                  ),
                ),
                TextSpan(
                  text: '\n${_words.length.toString()}',
                  style: TextStyle(
                    decoration: TextDecoration.none,
                    fontWeight: FontWeight.bold,
                    color: Colors.white,
                    fontSize: 12,
                  ),
                ),
                TextSpan(
                  text: '\nLetter',
                  style: TextStyle(
                    decoration: TextDecoration.none,
                    color: Colors.white,
                    fontSize: 10,
                  ),
                ),
              ],
            ),
          ),
        ),
        centerTitle: true,
        title: !isSearching
            ? Text('My Title')
            : TextField(
                autofocus: true,
                style: TextStyle(color: Colors.white),
                controller: myController,
                onChanged: (value) {
                  value = value.toLowerCase();
                  setState(
                    () {
                      _wordsForDisplay = _words.where(
                        (word) {
                          var wordTitle = word.word.toLowerCase();
                          return wordTitle.contains(value);
                        },
                      ).toList();
                    },
                  );
                  setState(
                    () {
                      _wordsForDisplay = _words.where(
                        (word) {
                          var wordPronounce = word.pronunciation.toLowerCase();
                          return wordPronounce.contains(value);
                        },
                      ).toList();
                    },
                  );
                },
                decoration: InputDecoration(
                  isCollapsed: true,
                  icon: Icon(
                    Icons.menu_book,
                    color: Colors.white,
                  ),
                  hintText: 'Search',
                  hintStyle: TextStyle(color: Colors.white),
                ),
              ),
        actions: [
          isSearching
              ? IconButton(
                  icon: Icon(Icons.cancel_outlined),
                  onPressed: () {
                    setState(
                      () {
                        this.isSearching = false;
                        myController.clear();
                        _wordsForDisplay = _words.where(
                          (word) {
                            var wordTitle = word.word.toLowerCase();
                            return wordTitle.contains(wordTitle);
                          },
                        ).toList();
                      },
                    );
                  },
                )
              : IconButton(
                  icon: Icon(Icons.search_sharp),
                  onPressed: () {
                    setState(
                      () {
                        this.isSearching = true;
                      },
                    );
                  },
                ),
        ],
      ),
    );
  }

  FutureBuilder<List<Word>> myFutureBuilder() {
    return FutureBuilder(
      //future: getWord(),
      future:getWordFuture,
      builder: (context, AsyncSnapshot<List<Word>> snapshot) {
        if (snapshot.hasData) {
          return myWordListView(snapshot);
        } else {
          return Center(
            child: CircularProgressIndicator(),
          );
        }
      },
    );
  }

  ListView myWordListView(AsyncSnapshot<List<Word>> snapshot) {
    return ListView.builder(
      itemCount: _wordsForDisplay.length,
      itemBuilder: (context, index) {
        return ExpansionTile(
          title: Text(
            _wordsForDisplay[index].word,
            style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16.0),
          ),
          subtitle: Text(
            snapshot.data[index].pronunciation[0].toUpperCase() +
                snapshot.data[index].pronunciation.substring(1),
          ),
          leading: CircleAvatar(
            child: Text(snapshot.data[index].word[0]),
          ),
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Container(
                  width: MediaQuery.of(context).size.width,
                  child: Padding(
                    padding: const EdgeInsets.symmetric(
                        vertical: 7.0, horizontal: 19.0),
                    child: RichText(
                      text: TextSpan(
                        style: DefaultTextStyle.of(context).style,
                        children: <TextSpan>[
                          TextSpan(
                            text: snapshot.data[index].word + ' : ',
                            style: TextStyle(fontWeight: FontWeight.bold),
                          ),
                          TextSpan(text: snapshot.data[index].meaning),
                        ],
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ],
        );
      },
    );
  }
}

loic Ngou
  • 21
  • 4
  • Sorry but this is not working. I have tried this over and over already. I don't know why but it doesn't work. – OneMore Mar 04 '21 at 12:58
  • This unfortunately doesn't fix the issue. Stateful widgets in flutter persist data at the *widget* level, meaning it persists repaints but not being taken out of/being put into the widget tree. The data here needs to be stored via state management: https://flutter.dev/docs/development/data-and-backend/state-mgmt/options – dominicm00 Mar 05 '21 at 02:46