named pipe flutter

First let's examine the behavior of named pipes on linux:

mkfifo /tmp/npeg

The next command has the result of listening to the named pipe:

cat /tmp/npeg

This will keep running until something writes to the pipe. From another shell:

echo 'Hello' > /tmp/npeg

Now we see the result in the shell that ran cat.

$ cat /tmp/npeg
hello

Now for the flutter code:

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Named Pipe Stream Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: StreamListenerPage(),
    );
  }
}

class StreamListenerPage extends StatefulWidget {
  @override
  _StreamListenerPageState createState() => _StreamListenerPageState();
}

class _StreamListenerPageState extends State<StreamListenerPage> {
  StreamSubscription<String>? _streamSubscription;
  String _currentState = 'unknown';
  List<String> _events = [];
  bool _isListening = false;

  @override
  void initState() {
    super.initState();
    _startListening();
  }

  @override
  void dispose() {
    _streamSubscription?.cancel();
    super.dispose();
  }

  void _startListening() {
    setState(() {
      _isListening = true;
    });

    _streamSubscription = _createPipeStream().listen(
      (data) {
        setState(() {
          _events.add('${DateTime.now()}: $data');

          // Simple state detection based on message content
          if (data.toLowerCase().contains('signed in') ||
              data.toLowerCase().contains('authenticated') ||
              data.toLowerCase().contains('logged in')) {
            _currentState = 'signed_in';
          } else if (data.toLowerCase().contains('signed out') ||
              data.toLowerCase().contains('logged out') ||
              data.toLowerCase().contains('unauthenticated')) {
            _currentState = 'signed_out';
          }
        });
      },
      onError: (error) {
        setState(() {
          _events.add('Error: $error');
          _isListening = false;
        });
      },
      onDone: () {
        setState(() {
          _isListening = false;
        });
      },
    );
  }

  Stream<String> _createPipeStream() {
    final controller = StreamController<String>();

    // Path to your named pipe
    const pipePath = '/tmp/auth_stream';

    Timer.periodic(Duration(milliseconds: 500), (timer) async {
      try {
        final file = File(pipePath);

        if (await file.exists()) {
          // Try to read from the pipe (non-blocking)
          try {
            final process = await Process.start('timeout', [
              '0.1',
              'cat',
              pipePath,
            ]);

            process.stdout.transform(const SystemEncoding().decoder).listen((
              data,
            ) {
              final lines = data.trim().split('\n');
              for (final line in lines) {
                if (line.isNotEmpty) {
                  controller.add(line);
                }
              }
            });

            await process.exitCode;
          } catch (e) {
            // Pipe might be empty or blocked, continue listening
          }
        }
      } catch (e) {
        controller.addError('Failed to read from pipe: $e');
        timer.cancel();
      }
    });

    return controller.stream;
  }

  void _stopListening() {
    _streamSubscription?.cancel();
    setState(() {
      _isListening = false;
    });
  }

  void _clearEvents() {
    setState(() {
      _events.clear();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _buildCurrentScreen(),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: _isListening ? _stopListening : _startListening,
            child: Icon(_isListening ? Icons.stop : Icons.play_arrow),
            backgroundColor: _isListening ? Colors.red : Colors.green,
            heroTag: "listen_btn",
          ),
          SizedBox(height: 10),
          FloatingActionButton(
            onPressed: _clearEvents,
            child: Icon(Icons.clear),
            backgroundColor: Colors.orange,
            heroTag: "clear_btn",
          ),
        ],
      ),
    );
  }

  Widget _buildCurrentScreen() {
    switch (_currentState) {
      case 'signed_in':
        return _buildSignedInScreen();
      case 'signed_out':
        return _buildSignedOutScreen();
      default:
        return _buildUnknownScreen();
    }
  }

  Widget _buildSignedInScreen() {
    return Container(
      decoration: BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topCenter,
          end: Alignment.bottomCenter,
          colors: [Colors.green.shade300, Colors.green.shade600],
        ),
      ),
      child: Column(
        children: [
          Expanded(
            flex: 2,
            child: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.check_circle, size: 120, color: Colors.white),
                  SizedBox(height: 20),
                  Text(
                    'Welcome!',
                    style: TextStyle(
                      fontSize: 32,
                      fontWeight: FontWeight.bold,
                      color: Colors.white,
                    ),
                  ),
                  SizedBox(height: 10),
                  Text(
                    'User is signed in',
                    style: TextStyle(fontSize: 18, color: Colors.white70),
                  ),
                ],
              ),
            ),
          ),
          _buildEventsList(),
        ],
      ),
    );
  }

  Widget _buildSignedOutScreen() {
    return Container(
      decoration: BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topCenter,
          end: Alignment.bottomCenter,
          colors: [Colors.red.shade300, Colors.red.shade600],
        ),
      ),
      child: Column(
        children: [
          Expanded(
            flex: 2,
            child: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.cancel, size: 120, color: Colors.white),
                  SizedBox(height: 20),
                  Text(
                    'Please Sign In',
                    style: TextStyle(
                      fontSize: 32,
                      fontWeight: FontWeight.bold,
                      color: Colors.white,
                    ),
                  ),
                  SizedBox(height: 10),
                  Text(
                    'User is currently signed out',
                    style: TextStyle(fontSize: 18, color: Colors.white70),
                  ),
                ],
              ),
            ),
          ),
          _buildEventsList(),
        ],
      ),
    );
  }

  Widget _buildUnknownScreen() {
    return Container(
      decoration: BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topCenter,
          end: Alignment.bottomCenter,
          colors: [Colors.grey.shade400, Colors.grey.shade700],
        ),
      ),
      child: Column(
        children: [
          Expanded(
            flex: 2,
            child: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.help_outline, size: 120, color: Colors.white),
                  SizedBox(height: 20),
                  Text(
                    'Listening...',
                    style: TextStyle(
                      fontSize: 32,
                      fontWeight: FontWeight.bold,
                      color: Colors.white,
                    ),
                  ),
                  SizedBox(height: 10),
                  Text(
                    _isListening
                        ? 'Waiting for auth events from /tmp/auth_stream'
                        : 'Not listening to pipe',
                    style: TextStyle(fontSize: 16, color: Colors.white70),
                    textAlign: TextAlign.center,
                  ),
                ],
              ),
            ),
          ),
          _buildEventsList(),
        ],
      ),
    );
  }

  Widget _buildEventsList() {
    return Expanded(
      flex: 1,
      child: Container(
        decoration: BoxDecoration(
          color: Colors.black87,
          borderRadius: BorderRadius.only(
            topLeft: Radius.circular(20),
            topRight: Radius.circular(20),
          ),
        ),
        child: Column(
          children: [
            Container(
              padding: EdgeInsets.all(16),
              child: Row(
                children: [
                  Icon(Icons.event_note, color: Colors.white70),
                  SizedBox(width: 8),
                  Text(
                    'Stream Events (${_events.length})',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  Spacer(),
                  Container(
                    padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                    decoration: BoxDecoration(
                      color: _isListening ? Colors.green : Colors.red,
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: Text(
                      _isListening ? 'LIVE' : 'STOPPED',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 12,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ),
                ],
              ),
            ),
            Expanded(
              child: _events.isEmpty
                  ? Center(
                      child: Text(
                        'No events received yet',
                        style: TextStyle(color: Colors.white54),
                      ),
                    )
                  : ListView.builder(
                      reverse: true,
                      itemCount: _events.length,
                      itemBuilder: (context, index) {
                        final event = _events[_events.length - 1 - index];
                        return Container(
                          margin: EdgeInsets.symmetric(
                            horizontal: 16,
                            vertical: 2,
                          ),
                          padding: EdgeInsets.all(12),
                          decoration: BoxDecoration(
                            color: Colors.white10,
                            borderRadius: BorderRadius.circular(8),
                          ),
                          child: Text(
                            event,
                            style: TextStyle(
                              color: Colors.white,
                              fontFamily: 'monospace',
                              fontSize: 12,
                            ),
                          ),
                        );
                      },
                    ),
            ),
          ],
        ),
      ),
    );
  }
}

Comments