skip to Main Content

I have been using a FutureBuilder to read data from an api, and update the UI when it completes. All good, but now I want to refresh this every 5 minutes. I thought I could do this with a Timer. My code

class _MyTileState extends State<MyTile> {

  late Future<MyData?> myFuture = fetchMyData();

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        FutureBuilder<MyData?>(
          future: myFuture, 
          builder: (BuildContext context, AsyncSnapshot<MyData?> snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return CircularProgressIndicator();
            } else if (snapshot.hasError) {
              return Text('Error: ${snapshot.error}');
            } else {
              return Text(snapshot.data.title);
            }
          },
        ),
      ],
    );
  }

  Future<MyData?> fetchMyData() async {
    var myData = await readMyData();
    return myData;
  }

If I add a timer I get an error message : The instance member ‘fetchMyData’ can’t be accessed in an initializer

Timer timer = Timer(Duration(minutes: 5), () {
    myFuture = fetchMyData();
    setState(() {});
});

While trying to sort this, it has become apparent that FutureBuilder should not be used to refresh. Instead use a StreamBuilder. But this just displays the CircularProgressIndicator and only appears to run once

  StreamBuilder<MyData>(
    stream: fetchMyDataStream(),
    builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.active) {
      return Text(snapshot.data.title);
    }

    return CircularProgressIndicator();
    }
  ),


  Stream<MyData> get fetchMyDataStream() async* {
    print("start fetch my data stream");
    
    await Future.delayed(Duration(seconds: 5)); // set to seconds for testing

    var myData = await readMyData();
    yield myData;
  }
}

I am trying to work out which way I should be going – FutureBuilder or StreamBuilder? And for the preferred option, what I am doing wrong.

2

Answers


  1. You can use Stream Builder and Stream Controller for that. There are many other ways to do so but this is one of the answer

    import 'dart:async'; // Importing dart:async for using Timer class
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter StreamBuilder Example',
          home: StreamExample(),
        );
      }
    }
    
    class StreamExample extends StatefulWidget {
      @override
      _StreamExampleState createState() => _StreamExampleState();
    }
    
    class _StreamExampleState extends State<StreamExample> {
      late StreamController<int> _controller; // Declaring StreamController for integer values
      late Timer _timer; // Declaring Timer for periodic updates
      int _counter = 0; // Counter variable to keep track of updates
    
      @override
      void initState() {
        super.initState();
        _controller = StreamController<int>(); // Initializing StreamController
        _timer = Timer.periodic(Duration(seconds: 10), (timer) {
          // Creating a periodic timer that triggers every 10 seconds
          _counter++; // Incrementing counter value
          _controller.add(_counter); // Adding updated counter value to the stream
        });
      }
    
      @override
      void dispose() {
        _controller.close(); // Closing StreamController to release resources
        _timer.cancel(); // Cancelling the timer to stop periodic updates
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Flutter StreamBuilder Example'),
          ),
          body: Center(
            child: StreamBuilder<int>(
              stream: _controller.stream, // Providing the stream from StreamController
              initialData: 0, // Initial data for the StreamBuilder
              builder: (context, snapshot) {
                return Text(
                  'Value from StreamBuilder: ${snapshot.data}',
                  style: TextStyle(fontSize: 20),
                );
              },
            ),
          ),
        );
      }
    }
    
    Login or Signup to reply.
  2. You may simplify it:

    1. use initState() for an initial fetch and to start a periodic timer. You can update the UI (loading, loaded, error).
    2. do not forget to dispose of timer.

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search