Welcome to another educational blog post dedicated to Flutter developers. In this post, we're going to explore one of the essential classes of the Flutter reactive programming paradigm: ReplaySubject.
ReplaySubject is a special StreamController in Flutter furnished by the RxDart package. This class captures all the items added to the controller and emits those as the first items to any new listener. With its ability to broadcast multiple subscriptions and store data, the ReplaySubject class is instrumental in leveraging the full power of reactive programming in Flutter.
With ReplaySubject, as soon as you create a subject stream, you can maintain a buffer of events and deliver stored data to new subscriptions. This opens up many possibilities for developers, such as allowing subscribers to receive an initial value or old data, keeping track of the most recently emitted item to new subscribers, and many more.
This blog post aims to give you an in-depth understanding of the ReplaySubject class and how to implement it effectively in your Flutter projects. You'll also learn to compare ReplaySubject with BehaviorSubject and their critical differences.
The ReplaySubject class is part of the RxDart package in Flutter. It has been designed to deal with asynchronous data streams proficiently.
At its core, a ReplaySubject is a StreamController, but with an additional ability to emit stored events from the past to new listeners. This means that whenever items (data) are added to the subject, the ReplaySubject stores them. When the stream is listened to, the ReplaySubject first emits these recorded data to the listener. After this initial flow of stored data, any new events are appropriately sent to the existing listeners.
Let's look at a very simple example of creating an instance of ReplaySubject and adding values to it.
1import 'package:rxdart/rxdart.dart'; 2 3void main() { 4 final subject = ReplaySubject<int>(); 5 6 subject.add(1); 7 subject.add(2); 8 subject.add(3); 9 10 subject.stream.listen(print); // prints 1, 2, 3 11}
In the example above, a ReplaySubject only takes int values (ReplaySubject<int>
). We added three integers (1, 2, and 3) into our subject's stream using the subject.add() method. When we subscribe a listener to this stream with subject.stream.listen(print);, it receives and prints all the integers added to the stream. This shows how all elements added to the stream are stored and then emitted to the listener.
By default, ReplaySubject is a broadcast (or hot) controller, meaning the subject's stream can be listened to multiple times. This is to fulfill the Rx Subject contract in Flutter.
Before we dig deeper into the features of ReplaySubject, it's crucial to understand how it differs from another type of subject in reactive programming known as BehaviorSubject. Both are subject types and a part of the RxDart library but serve different purposes.
BehaviorSubject is a type of Subject in Flutter that requires an initial value and replays just the latest value to new subscribers or listeners. In contrast, ReplaySubject doesn't demand an initial value and can replay all or a specified number of events to new subscribers, depending on the maxSize value passed at its creation. It can retain all or a specified number of most recent events and replay them to any new listener.
Let's consider an example to understand the difference. Suppose we have a BehaviorSubject and a ReplaySubject and we subscribe to them after two events have been added:
1import 'package:rxdart/rxdart.dart'; 2 3void main() { 4 final BehaviorSubject behaviorSubject = BehaviorSubject<int>(); 5 final ReplaySubject replaySubject = ReplaySubject<int>(); 6 7 behaviorSubject.add(1); 8 behaviorSubject.add(2); 9 10 replaySubject.add(1); 11 replaySubject.add(2); 12 13 behaviorSubject.stream.listen((value) => print('BehaviorSubject $value')); // Prints 2 14 replaySubject.stream.listen((value) => print('ReplaySubject $value')); // Prints 1, 2 15}
Here, the BehaviorSubject only emits the latest value (i.e., 2) to the new listener, while ReplaySubject emits all the values that have been added (i.e., 1 and 2).
While both BehaviorSubject and ReplaySubject are useful, the choice primarily depends on whether you want to replay only the single, latest event or all (or a specified number of) the past events to new subscribers.
Creating the instance of ReplaySubject is simple and easy. For this, first, you need to import the RxDart package into your Dart code. The next step is to declare and initialize the instance of the ReplaySubject class. Look at the following code snippet to understand better:
1import 'package:rxdart/rxdart.dart'; 2 3void main() { 4 final subject = ReplaySubject<int>(); 5 6 subject.add(1); 7 subject.add(2); 8 subject.add(3); 9 10 subject.stream.listen(print); // prints 1, 2, 3 11 subject.stream.listen(print); // prints 1, 2, 3 12}
In this piece of code, you'll notice that the subject.stream.listen(print); the line is repeated twice. That is to show ReplaySubject's ability to broadcast to multiple subscribers. As ReplaySubject is, by default, a broadcast (aka hot) controller, the Subject's stream can be listened to multiple times. Hence, two separate calls to subject.stream.listen(print); print the same values 1, 2, and 3, showing that both are getting the stored values from ReplaySubject.
This is a simple example, but it demonstrates the key concept of ReplaySubject, i.e., storing and replaying events to new listeners, whether it's a single-value event or a multi-value stream of data.
One of the unique features of the ReplaySubject class is the ability to cap the number of stored events, achieved by setting a maxSize value.
When you create an instance of the ReplaySubject class, there's an optional parameter, maxSize. By setting this parameter, you can limit the number of events stored by the ReplaySubject, thereby controlling the number of past events emitted to new subscribers.
Consider the following example:
1import 'package:rxdart/rxdart.dart'; 2 3void main() { 4 final subject = ReplaySubject<int>(maxSize: 2); 5 6 subject.add(1); 7 subject.add(2); 8 subject.add(3); 9 10 subject.stream.listen(print); // prints 2, 3 11 subject.stream.listen(print); // prints 2, 3 12}
Here, we set the maxSize to 2 while creating the ReplaySubject instance. We then add three integers to the subject. Due to the maxSize limitation, only the two most recently added integers (2 and 3) are stored and emitted to the listener when subscribing, demonstrating how maxSize effectively limits the number of stored events.
One thing to note here is that maxSize is optional. If no value is specified, the ReplaySubject will store and broadcast all the values added to it to the new listeners.
To fully grasp the functionality of ReplaySubject, it's essential to understand its standing in the inheritance hierarchy and the type of classes it implements.
Looking at the inheritance structure:
1Object > Stream<T> > StreamView<T> > Subject<T> > ReplaySubject
ReplaySubject is a direct subclass of Subject<T>
, which, in turn, inherits from StreamView<T>
and Stream<T>
. Ultimately, like all classes in Dart, ReplaySubject is a descendant of the base Object class.
The ReplaySubject also implements a special type: ReplayStream<T>
. This is a specific kind of stream that supports replaying stored values of a broadcast stream.
The ReplaySubject class has many extensions, expanding its versatility and allowing developers to access a wide array of functionalities. Here, we'll discuss some of these extensions and what they offer.
BufferExtensions: provides capabilities to buffer a Stream of events under various conditions.
ConcatExtensions: enables the concatenation of two streams.
ConnectableStreamExtensions: makes a stream connectable, enabling the control of when the stream begins emitting items.
DebounceExtensions: emits only the most recent item emitted by the source Stream since the previous emission from the DebounceStreamTransformer.
DelayExtension: introduces a delay between the source Stream and the returned Stream.
These are only a few examples of the extensions available with the ReplaySubject class. Other variants such as MaxExtension, SwitchMapExtension, TimeIntervalExtension, WhereNotNullExtension, and more offer numerous possibilities to manipulate and transform streams according to your application requirements.
The ReplaySubject class in Flutter provides a rich set of constructors, properties, and methods to handle data streams efficiently. Let's delve a bit deeper into these.
ReplaySubject provides a straightforward constructor as shown below:
1ReplaySubject({int? maxSize, void onListen()?, void onCancel()?, bool sync = false})
This constructor enables the creation of a ReplaySubject. It also allows you to pass handlers for onListen and onCancel and a flag to handle events synchronously (sync).
ReplaySubject has several properties, such as done, first, hashCode, isBroadcast, length, onListen, onResume, single, and more. For instance, isBroadcast returns whether the stream is a broadcast stream, and done returns a future that completes when the stream controller sends events.
One of the essential methods provided by ReplaySubject is add(T event). It sends a data event to be stored and emitted to listeners. Others include addError(Object error, [StackTrace? stackTrace]), addStream(Stream<T> source, {bool? cancelOnError})
, and cast<R>
(), to name a few.
To appropriately use these constructors, properties, and methods, it's recommended that you have a good understanding of streams in Dart and reactive programming principles.
In the upcoming sections, we'll delve into practical examples and scenarios to help you apply these concepts better.
The power of ReplaySubject becomes evident when dealing with asynchronous events. By allowing you to store and replay past events to new subscribers, ReplaySubject simplifies the handling of asynchronous data.
Consider an example where our ReplaySubject is tasked to work with three events that occur asynchronously:
1import 'package:rxdart/rxdart.dart'; 2 3void main() async { 4 final subject = ReplaySubject<int>(); 5 6 await Future.delayed(const Duration(milliseconds: 200), () => subject.add(1)); 7 8 await Future.delayed(const Duration(milliseconds: 100), () => subject.add(2)); 9 10 await Future.delayed(const Duration(milliseconds: 300), () => subject.add(3)); 11 12 subject.stream.listen(print); 13}
In this code, each call to Future.delayed() creates a future that completes after delaying for a specified duration (in milliseconds). Within this delay, we're adding an integer to ReplaySubject. The subject.stream.listen(print); will print the numbers 1, 2, and 3 in order, despite their asynchronous addition to the ReplaySubject.
Mastering the ReplaySubject class is a pivotal step in your journey as a Flutter developer. As one of the many types of Subject in reactive programming within Flutter, ReplaySubject offers a distinct feature set for streaming and replaying stored events to new subscribers. By broadcasting and replaying past events, ReplaySubject empowers developers to easily manage complex, asynchronous streams.
In this blog post, we deep-dived into the key characteristics of the ReplaySubject class, including its properties, methods, and implemented types. We also highlighted its standing in the inheritance hierarchy and unpacked how to use it to handle asynchronous events. Moreover, we touched on the essential differences between ReplaySubject and BehaviorSubject, giving you critical prerequisite knowledge for selecting the right type of Subject based on the application requirements.
As you continue developing in Flutter, remember that ReplaySubject is just one part of your rich reactive programming tools.
Happy Fluttering 💙!
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.