RxDart - Using Materialize and Dematerialize

This tutorial shows you how to use materialize() and dematerialize() operators in RxDart.

Normally, an Observable will invoke the onData method of its observers for zero or multiple times. After that, it will invoke either onDone or onError. That means there are three kinds of event and you may need to have different handlers for each kind of event. By using Materialize, it becomes possible to have a general handler for all kinds of event. Materialize works by projecting a Stream of values into a stream of Notification objects.

Notification has the following properties:

Name Type Description
kind Kind Kind of the event. (Kind.OnData, Kind.OnDone, or Kind.OnError)
value T -
error dynamic -
stackTrace StackTrace -

It's also possible to do the opposite thing: dematerializing Notification objects. The operators for materialize and dematerialize are available in RxDart. For usage examples, see the below examples. 

Materialize Example

For converting onData, onDone, and onError events into Notification objects, Dart provides materialize() operator.

  import 'package:rxdart/rxdart.dart';
  
  class MyException implements Exception {
  
    String _message;
  
    MyException([String message = 'Invalid value']) {
      this._message = message;
    }
  
    @override
    String toString() {
      return _message;
    }
  }
  
  void main(List<String> arguments) {
    Observable.range(1, 4)
        .map((value) => value < 4 ? value : throw Observable.error(new MyException()))
        .materialize()
        .listen(print);
  }

Output:

  Notification{kind: Kind.OnData, value: 1, error: null, stackTrace: null}
  Notification{kind: Kind.OnData, value: 2, error: null, stackTrace: null}
  Notification{kind: Kind.OnData, value: 3, error: null, stackTrace: null}
  Notification{kind: Kind.OnError, value: null, error: Instance of 'Observable<dynamic>', stackTrace: #0      main.<anonymous closure> (file:///home/ivan/try-dart/src/rxdart-materialize.dart:36:43)
  #1      _MapStream._handleData (dart:async/stream_pipe.dart:229:21)
  #2      _ForwardingStreamSubscription._handleData (dart:async/stream_pipe.dart:166:13)
  #3      _RootZone.runUnaryGuarded (dart:async/zone.dart:1316:10)
  #4      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:338:11)
  #5      _IterablePendingEvents.handleNext (dart:async/stream_impl.dart:539:18)
  #6      _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:669:7)
  #7      _microtaskLoop (dart:async/schedule_microtask.dart:43:21)
  #8      _startMicrotaskLoop (dart:async/schedule_microtask.dart:52:5)
  #9      _runPendingImmediateCallback (dart:isolate-patch/isolate_patch.dart:118:13)
  #10     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:175:5)
  }
  Notification{kind: Kind.OnDone, value: null, error: null, stackTrace: null}

As you can see, the output are Notification objects that have properties as mentioned on the above table.

Dematerialize Example

In case you need to dematerialize Notification objects into onData, onDone, and onError events, you can use dematerialize() operator.

  Observable<Notification<int>> o = Observable.range(1, 4)
      .map((value) => value < 4 ? value : throw Observable.error(new MyException()))
      .materialize();

  o.dematerialize()
      .listen(print);

Output:

  1
  2
  3
  Unhandled exception:
  Instance of 'Observable<dynamic>'
  #0      main.<anonymous closure> (file:///home/ivan/try-dart/src/rxdart-materialize.dart:44:43)
  #1      _MapStream._handleData (dart:async/stream_pipe.dart:229:21)
  #2      _ForwardingStreamSubscription._handleData (dart:async/stream_pipe.dart:166:13)
  #3      _RootZone.runUnaryGuarded (dart:async/zone.dart:1316:10)
  #4      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:338:11)
  #5      _IterablePendingEvents.handleNext (dart:async/stream_impl.dart:539:18)
  #6      _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:669:7)
  #7      _microtaskLoop (dart:async/schedule_microtask.dart:43:21)
  #8      _startMicrotaskLoop (dart:async/schedule_microtask.dart:52:5)
  #9      _runPendingImmediateCallback (dart:isolate-patch/isolate_patch.dart:118:13)
  #10     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:175:5)