Refresh Flutter Text widget content every 5 minutes or periodically

isudarsan picture isudarsan · Dec 25, 2018 · Viewed 15.7k times · Source

I have a Flutter Text widget and its content is populated from an external REST call.I would like to refresh the widget content periodically every 5 mins by calling the REST endpoint.

So far I managed to call the endpoint every 5 mins but unable to update/refresh the widget content with new data from network.

 class PatientCount {
  int count;
  double amount;

PatientCount({this.count, this.amount});

 PatientCount.fromJson(Map<String, dynamic> map)
  : count = map['count'],
    amount = map['amount'];
}

Future<PatientCount> fetchPatientCount() async {

  var url = "http://localhost:9092/hms/patients-count-on-day";

  Map<String, String> requestHeaders = new Map<String, String>();
  requestHeaders["Accept"] = "application/json";
  requestHeaders["Content-type"] = "application/json";

  String requestBody = '{"consultedOn":' + '16112018' + '}';  

  http.Response response =
  await http.post(url, headers: requestHeaders, body: requestBody);

  final statusCode = response.statusCode;
  final Map responseBody = json.decode(response.body);

  if (statusCode != 200 || responseBody == null) {
     throw new FetchPatientCountException(
    "Error occured : [Status Code : $statusCode]");
   }
    return PatientCount.fromJson(responseBody['responseData']. 
    ['PatientCountDTO']);
}    
class MainPage extends StatefulWidget {
  @override
  _MainPageState createState() => _MainPageState();
 }

class _MainPageState extends State<MainPage> {
@override
void initState() {
super.initState();
setState(() {
  const oneSecond = const Duration(seconds: 25);
  new Timer.periodic(oneSecond, (Timer t) => buildCountWidget());
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
    appBar: AppBar(
      elevation: 2.0,
      backgroundColor: Colors.white,
      title: Text('Dashboard'),
    ),
    body: StaggeredGridView.count(
      crossAxisCount: 2,
      crossAxisSpacing: 12.0,
      mainAxisSpacing: 12.0,
      padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
      children: <Widget>[
        _buildTile(
          Padding(
            padding: const EdgeInsets.all(24.0),
            child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      Text(
                        'Today\'s OPD',
                        style: TextStyle(
                            color: Colors.blueAccent, fontSize: 18.0),
                      ),
                      buildCountWidget(),
                    ],
                  ),
                  Material(
                      color: Colors.blue,
                      borderRadius: BorderRadius.circular(24.0),
                      child: Center(
                          child: Padding(
                        padding: const EdgeInsets.all(16.0),
                        child: Icon(Icons.timeline,
                            color: Colors.white, size: 30.0),
                      )))
                ]),
          ),
        ),
      ],
      staggeredTiles: [StaggeredTile.extent(2, 110.0)],
    ));
  }

 Widget _buildTile(Widget child, {Function() onTap}) {
  return Material(
    elevation: 14.0,
    borderRadius: BorderRadius.circular(12.0),
    shadowColor: Color(0x802196F3),
    child: InkWell(
        // Do onTap() if it isn't null, otherwise do print()
        onTap: onTap != null
            ? () => onTap()
            : () {
                print('Not set yet');
              },
        child: child));
  }

 Widget buildCountWidget() {
 Widget vistitCount = new Center(
  child: new FutureBuilder<PatientCount>(
    future: fetchPatientCount(),
    builder: (context, snapshot) {          
      if (snapshot.connectionState == ConnectionState.done) {
        if (snapshot.hasData) {
          print(snapshot.data.count);
          /* below text needs to be updated every 5 mins or so */
          return new Text('#' + snapshot.data.count.toString(),
              style: TextStyle(
                  color: Colors.black,
                  fontWeight: FontWeight.w700,
                  fontSize: 34.0));
        } else if (snapshot.hasError) {
          return new Text("${snapshot.error}");
        }
      }

      // By default, show a loading spinner
      return new CircularProgressIndicator();
    },
  ),
);
return vistitCount;
}
}

Inside the buildCountWidget method the Text widget needs to be refreshed with the latest data from the network.

I changed the implementation to use setState as below, still no luck

class _MainPageState extends State<MainPage> {
  Future<PatientCount> _patientCount;
  Timer timer;
  @override
  void initState() {
    super.initState();
    callApi();
    timer = Timer.periodic(Duration(seconds: 15), (Timer t) => setState(() {}));
  }

  void callApi() {
    setState(() {
      _patientCount = fetchPatientCount();
    });
  }
 ..........................

Also changed the logic as below, with this I am able to call the REST endpoint but the widget data is not getting updated every 25 seconds.The widget is showing the old data .

class _MainPageState extends State<MainPage> {
  Future<PatientCount> _patientCount;
  Timer timer;
  @override
  void initState() {
    super.initState();
    //callApi();
    timer = Timer.periodic(Duration(seconds: 15), (Timer t) => callApi());
  }

  void callApi() {
    setState(() {
      _patientCount = fetchPatientCount();
    });
  }

...........................

As per the code it is showing the same count , the count is not getting incremented after 25 seconds.However from the backend the Api is fired periodically and returning the data to UI, but the state to the widget is not changing.

here the count is not changing every 25 seconds

Answer

diegoveloper picture diegoveloper · Dec 25, 2018

Replace this :

new Timer.periodic(oneSecond, (Timer t) => buildCountWidget());

By this:

new Timer.periodic(oneSecond, (Timer t) => setState((){}));

And it should work, every time you call setState it'll refresh the widget and will call to the Future method again.

UPDATE

It's working fine, if you make these changes, you will notice how the data is refreshed (just for testing):

        Future<String> fetchPatientCount() async {
          print("fetchPatientCount");
          return DateTime.now().toIso8601String();
        }

        ...

        new FutureBuilder<String>(
                future: fetchPatientCount(),
                builder: (context, snapshot) {
                  if (snapshot.connectionState == ConnectionState.done) {
                    if (snapshot.hasData) {            
                      /* below text needs to be updated every 5 mins or so */
                      return new Text('#' + snapshot.data.toString(),
                          style: TextStyle(
                              color: Colors.black,
                              fontWeight: FontWeight.w700,
                              fontSize:7.0));
                    } else if (snapshot.hasError) {
                      return new Text("${snapshot.error}");
                    }
                  }

If the data changes every 25 seconds, it's working , you have to check your fetchPatientCount method. ( encode the data to json before send requestBody)