flutter_multi_stream_builder

A Flutter widget that efficiently handles multiple streams in a single widget tree, eliminating the need for nested StreamBuilder widgets.

pub package License: MIT

Features

Multiple Stream Support - Handle any number of streams simultaneously
🔒 Type Safety - Full generic type support for each stream
Efficient Rebuilding - Only rebuilds when any of the streams emit new data
🎯 Clean API - Simple builder pattern similar to StreamBuilder
🔄 Flutter Integration - Seamlessly integrates with existing Flutter applications

Getting Started

Installation

Add this package to your pubspec.yaml:

dependencies:
  flutter_multi_stream_builder: ^1.0.0

Then run:

flutter pub get

Basic Usage

import 'package:flutter_multi_stream_builder/flutter_multi_stream_builder.dart';

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiStreamBuilder(
      streams: {
        userStream,
        settingsStream,
        notificationsStream,
      },
      builder: (getSnapshot) {
        final userSnapshot = getSnapshot(userStream);
        final settingsSnapshot = getSnapshot(settingsStream);
        final notificationsSnapshot = getSnapshot(notificationsStream);

        if (userSnapshot.hasData && 
            settingsSnapshot.hasData && 
            notificationsSnapshot.hasData) {
          return UserProfileWidget(
            user: userSnapshot.data!,
            settings: settingsSnapshot.data!,
            notifications: notificationsSnapshot.data!,
          );
        }

        if (userSnapshot.hasError || 
            settingsSnapshot.hasError || 
            notificationsSnapshot.hasError) {
          return ErrorWidget('Something went wrong');
        }

        return CircularProgressIndicator();
      },
    );
  }
}

Advanced Usage

Handling Different Stream Types

You can handle streams of different types in the same builder:

MultiStreamBuilder(
  streams: {userStream, counterStream, messageStream},
  builder: (getSnapshot) {
    final user = getSnapshot(userStream).data as User?;
    final count = getSnapshot(counterStream).data as int?;
    final messages = getSnapshot(messageStream).data as List<Message>?;

    return Column(
      children: [
        if (user != null) UserCard(user: user),
        if (count != null) CounterDisplay(count: count),
        if (messages != null) MessageList(messages: messages),
      ],
    );
  },
)

Error Handling

Check for errors on individual streams:

final userSnapshot = getSnapshot(userStream);
if (userSnapshot.hasError) {
  return ErrorWidget('Failed to load user: ${userSnapshot.error}');
}

Loading States

Handle different loading states:

MultiStreamBuilder(
  streams: {dataStream, configStream},
  builder: (getSnapshot) {
    final dataSnapshot = getSnapshot(dataStream);
    final configSnapshot = getSnapshot(configStream);

    // Check if any stream is still loading
    if (dataSnapshot.connectionState == ConnectionState.waiting ||
        configSnapshot.connectionState == ConnectionState.waiting) {
      return CircularProgressIndicator();
    }

    // Check if any stream has an error
    if (dataSnapshot.hasError || configSnapshot.hasError) {
      return ErrorWidget('Failed to load data');
    }

    // All streams have data
    return MyWidget(
      data: dataSnapshot.data!,
      config: configSnapshot.data!,
    );
  },
)

Comparison with Nested StreamBuilder

Before (Nested StreamBuilder)

StreamBuilder<User>(
  stream: userStream,
  builder: (context, userSnapshot) {
    return StreamBuilder<Settings>(
      stream: settingsStream,
      builder: (context, settingsSnapshot) {
        return StreamBuilder<List<Notification>>(
          stream: notificationsStream,
          builder: (context, notificationsSnapshot) {
            // Complex nested logic here
            if (userSnapshot.hasData && 
                settingsSnapshot.hasData && 
                notificationsSnapshot.hasData) {
              return UserProfileWidget(
                user: userSnapshot.data!,
                settings: settingsSnapshot.data!,
                notifications: notificationsSnapshot.data!,
              );
            }
            return CircularProgressIndicator();
          },
        );
      },
    );
  },
)

After (MultiStreamBuilder)

MultiStreamBuilder(
  streams: {userStream, settingsStream, notificationsStream},
  builder: (getSnapshot) {
    final userSnapshot = getSnapshot(userStream);
    final settingsSnapshot = getSnapshot(settingsStream);
    final notificationsSnapshot = getSnapshot(notificationsStream);

    if (userSnapshot.hasData && 
        settingsSnapshot.hasData && 
        notificationsSnapshot.hasData) {
      return UserProfileWidget(
        user: userSnapshot.data!,
        settings: settingsSnapshot.data!,
        notifications: notificationsSnapshot.data!,
      );
    }
    return CircularProgressIndicator();
  },
)

API Reference

MultiStreamBuilder

A widget that builds itself based on the latest snapshots of multiple streams.

Constructor

const MultiStreamBuilder({
  Key? key,
  required Set<Stream> streams,
  required Widget Function(AsyncSnapshot<T> Function<T>(Stream<T>) getSnapshot) builder,
})

Parameters

  • streams (Set<Stream>) - The set of streams to listen to. Each stream in this set will be listened to and its snapshots will be available in the builder function.

  • builder (Widget Function(AsyncSnapshot<T> Function<T>(Stream<T>) getSnapshot)) - The builder function that creates the widget tree. This function is called whenever any of the streams emit new data.

Example

MultiStreamBuilder(
  streams: {stream1, stream2, stream3},
  builder: (getSnapshot) {
    final snapshot1 = getSnapshot(stream1);
    final snapshot2 = getSnapshot(stream2);
    final snapshot3 = getSnapshot(stream3);
    
    if (snapshot1.hasData && snapshot2.hasData && snapshot3.hasData) {
      return Text('All streams have data!');
    }
    return CircularProgressIndicator();
  },
)

Performance Considerations

  • The widget efficiently manages stream subscriptions and only rebuilds when necessary
  • Each stream is handled independently, so partial updates are possible
  • The builder function is called whenever any stream emits new data
  • Consider using Stream.periodic or other continuous streams carefully

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Additional Information

For more examples, see the /example folder in the package repository.

Libraries

flutter_multi_stream_builder
A Flutter package that provides a MultiStreamBuilder widget for efficiently handling multiple streams in a single widget tree.