flutterwebsocketbackground-serviceflutter-streambuilderflutter-background

How to Get Stream Data from WebSocket which is in Background Service to Main App in Flutter?


I am working on a Flutter project where I am using a background service (with flutter_background_service package) to handle a WebSocket connection. The WebSocket receives updates even when the app is in the background. My goal is to stream the WebSocket data received in the background service to the main app so that the UI can update in real-time.


final service = FlutterBackgroundService();

Future<void> initializeService() async {
  await service.configure(
    iosConfiguration: IosConfiguration(
        autoStart: true, onForeground: onStart, onBackground: onBackground),
    androidConfiguration: AndroidConfiguration(
        autoStartOnBoot: false,
        onStart: onStart,
        isForegroundMode: true,
        autoStart: false),
  );
}

IO.Socket? socket;

void connectWebSocket(String uniqueId) {
  const String serverUrl = 'http://test.buggyhub.com:4000/';
  socket = IO.io(
    serverUrl,
    IO.OptionBuilder()
        .setTransports(['websocket'])
        .disableAutoConnect()
        .build(),
  );

  socket?.connect();

  socket?.onConnect((_) {
    log('Connected to server with unique ID: $uniqueId');
    socket?.emit('sarthiId', uniqueId);
  });

  socket?.on('connectionResponse', (data) {
    log('Message from server: $data');
  });

  socket?.on('bookingUpdate', (data) async {
    log('Received bookingUpdate from server: $data');
    // I want to stream this data to the main app
  });

  socket?.onDisconnect((_) {
    log('Disconnected from server');
  });
}

@pragma('vm:entry-point')
void onStart(ServiceInstance service) async {
  WidgetsFlutterBinding.ensureInitialized();
  DartPluginRegistrant.ensureInitialized();

  if (service is AndroidServiceInstance) {
    service.on('setAsForeground').listen((event) {
      service.setAsForegroundService();
    });
    service.on('setAsBackground').listen((event) {
      service.setAsBackgroundService();
    });
    service.on('stopService').listen((event) {
      service.stopSelf();
    });
  }

  Timer.periodic(const Duration(seconds: 20), (timer) async {
    if (service is AndroidServiceInstance && await service.isForegroundService()) {
      log('Running background service');
      socket?.disconnect();
      connectWebSocket('uniqueId');
    }
  });

  await Firebase.initializeApp();
}
@pragma('vm:entry-point')
Future<bool> onBackground(ServiceInstance serviceInstance) async {
  WidgetsFlutterBinding.ensureInitialized();
  DartPluginRegistrant.ensureInitialized();
  await Firebase.initializeApp();
  return true;
}

My Issue:

How can I send the WebSocket data (received in the background service) to the main app as a stream so that it can be used to update the UI in real-time?

What I’ve Tried:

I’m aware of StreamController and Stream in Flutter but am unsure how to implement this with data from the WebSocket that is running in the background service.

I also want to maintain this stream connection even when the app is in the background and update the UI when the app is in the foreground.

Any help or suggestions on how to achieve this would be appreciated!


Solution

  • basic example

    import 'dart:async';
    
    import 'package:flutter/material.dart';
    
    import 'package:flutter_background_service/flutter_background_service.dart';
    
    Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await initializeService();
      runApp(const MyApp());
    }
    
    Future<void> initializeService() async {
      final service = FlutterBackgroundService();
    
      await service.configure(
        androidConfiguration: AndroidConfiguration(
          // this will be executed when app is in foreground or background in separated isolate
          onStart: onStart,
    
          // auto start service
          autoStart: false,
          isForegroundMode: false,
        ),
        iosConfiguration: IosConfiguration(
          // auto start service
          autoStart: false,
    
          // this will be executed when app is in foreground in separated isolate
          onForeground: onStart,
        ),
      );
    }
    
    void onStart(ServiceInstance service) async {
      service.on('toServer').listen((data) {
        ///////////////////////////////////////////////////
        //here you code use websocket to send data to server
      });
    
      ////////////////////////////////////////////////////////
      //here you code use websocket to receive data from server
      await Future.delayed(const Duration(seconds: 1));
      service.invoke('fromServer', {"data": "Text expample 1 secons"});
      await Future.delayed(const Duration(seconds: 5));
      service.invoke('fromServer', {"data": "Text expample 5 secons"});
    }
    
    class MyApp extends StatefulWidget {
      const MyApp({super.key});
    
      @override
      State<MyApp> createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      final bgService = FlutterBackgroundService();
    
      @override
      Widget build(BuildContext context) {
        final Stream<Map<String, dynamic>?> fromServer = bgService.on('fromServer');
    
        //Send data to server
        bgService.invoke('toServer', {"data": "Text expample"});
    
        return MaterialApp(
          title: 'Material App',
          home: Scaffold(
            appBar: AppBar(
              title: const Text('Material App Bar'),
            ),
            body: Center(
              child: StreamBuilder<Map<String, dynamic>?>(
                stream: fromServer,
                builder: (context, snapshot) {
                  if (!snapshot.hasData) {
                    return const CircularProgressIndicator();
                  }
                  return Text(
                    snapshot.data!['data'].toString(),
                    style: const TextStyle(fontSize: 24),
                  ); 
                },
              ),
            ),
          ),
        );
      }
    }