androidflutterdartflutter-streambuilder

How can I fix this stream in my Flutter app?


I am working on a Flutter app and I need to send a stream of data from my DatabaseHandler class to a stateful widget, but the listener isn't working (print statements confirm stream is being initialized and updated, print statement in listener method doesn't get called).

I've created an example version of how I have it set up below:

import 'package:flutter/material.dart';
import 'dart:async';

Future<void> main() async {
  runApp(const MyApp());

  Timer.periodic(
    const Duration(seconds: 4),
    (timer) {DatabaseHandler().startStream();
    }
  );
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  @override
  void initState(){
    DatabaseHandler().testStream.listen((int i) {
      print('Received location marker $i');
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Text('Hello World!'),
        ),
      ),
    );
  }
}

class DatabaseHandler{
  late StreamController<int> _testStreamController;
  Stream<int> get testStream => _testStreamController.stream;

  DatabaseHandler() {
    _testStreamController = StreamController<int>();
    print('Stream initialized');
  }

  void startStream() async {
    for (int i = 1; i <= 5; i++) {
        _testStreamController.add(i);
        print('Test stream: $testStream');
      }
  }
}

Solution

  • The issue here is that you're creating a new instance of DatabaseHandler in both main() and initState(). This means that the StreamController you're listening to in initState() is not the same one you're adding data to in main().

    To fix this, you should create a single instance of DatabaseHandler and use it in both places. Here's how you can do it:

    import 'package:flutter/material.dart';
    import 'dart:async';
    
    // Create a single instance of DatabaseHandler
    final databaseHandler = DatabaseHandler();
    
    Future<void> main() async {
      runApp(const MyApp());
      
      Timer.periodic(
        const Duration(seconds: 4),
        (timer) {databaseHandler.startStream(); 
        }
      );
    }
    
    class MyApp extends StatefulWidget {
      const MyApp({super.key});
    
      @override
      State<MyApp> createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
    
      @override
      void initState(){
        // Use the same instance of DatabaseHandler
        databaseHandler.testStream.listen((int i) {
          print('Received location marker $i');
        });
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: Scaffold(
            body: Center(
              child: Text('Hello World!'),
            ),
          ),
        );
      }
    }
    
    class DatabaseHandler{
      late StreamController<int> _testStreamController;
      Stream<int> get testStream => _testStreamController.stream;
    
      DatabaseHandler() {
        _testStreamController = StreamController<int>();
        print('Stream initialized');
      }
    
      void startStream() async {
        for (int i = 1; i <= 5; i++) {
            _testStreamController.add(i);
            print('Test stream: $testStream');
          }
      }
    }
    

    Now, the StreamController you're listening to in initState() is the same one you're adding data to in main(), so you should see the print statements from the listener.