using System; using System.Collections.Generic; using Unity.UIWidgets.async; using Unity.UIWidgets.foundation; namespace Unity.UIWidgets.widgets { public abstract class StreamBuilderBase : StatefulWidget { public StreamBuilderBase(Key key = null, Stream stream = null) : base(key: key) { this.stream = stream; } public readonly Stream stream; public abstract S initial(); public virtual S afterConnected(S current) => current; public abstract S afterData(S current, T data); public virtual S afterError(S current, object error) => current; public virtual S afterDone(S current) => current; public virtual S afterDisconnected(S current) => current; public abstract Widget build(BuildContext context, S currentSummary); public override State createState() => new _StreamBuilderBaseState(); } class _StreamBuilderBaseState : State> { StreamSubscription _subscription; S _summary; public override void initState() { base.initState(); _summary = widget.initial(); _subscribe(); } public override void didUpdateWidget(StatefulWidget statefulWidget) { StreamBuilderBase oldWidget = statefulWidget as StreamBuilderBase; if (oldWidget == null) { return; } base.didUpdateWidget(statefulWidget); if (oldWidget != null) { if (oldWidget.stream != widget.stream) { if (_subscription != null) { _unsubscribe(); _summary = widget.afterDisconnected(_summary); } _subscribe(); } } } public override Widget build(BuildContext context) => widget.build(context, _summary); public override void dispose() { _unsubscribe(); base.dispose(); } void _subscribe() { if (widget.stream != null) { _subscription = widget.stream.listen( (T data) => { setState(() => { _summary = widget.afterData(_summary, data); }); }, onError: (object error, string stackTrace) => { setState(() => { _summary = widget.afterError(_summary, error); }); }, onDone: () => { setState(() => { _summary = widget.afterDone(_summary); }); }); _summary = widget.afterConnected(_summary); } } void _unsubscribe() { if (_subscription != null) { _subscription.cancel(); _subscription = null; } } } public enum ConnectionState { none, waiting, active, done, } //@immutable public class AsyncSnapshot : IEquatable> { AsyncSnapshot(ConnectionState connectionState, object data, object error) { D.assert(connectionState != null); D.assert(!(data != null && error != null)); this.connectionState = connectionState; this.data = (T) data; this.error = error; } public static AsyncSnapshot nothing() { return new AsyncSnapshot(ConnectionState.none, null, null); } public static AsyncSnapshot withData(ConnectionState state, T data) { return new AsyncSnapshot(state, data, null); } public static AsyncSnapshot withError(ConnectionState state, object error) { return new AsyncSnapshot(state, null, error); } public readonly ConnectionState connectionState; public readonly T data; public T requireData { get { if (hasData) return data; if (hasError) //TODO: not sure if cast works throw (Exception) error; throw new Exception("Snapshot has neither data nor error"); } } public readonly object error; public AsyncSnapshot inState(ConnectionState state) { return new AsyncSnapshot(state, data, error); } public bool hasData { get => data != null; } public bool hasError { get => error != null; } public override string ToString() => $"{foundation_.objectRuntimeType(this, "AsyncSnapshot")}({connectionState}, {data}, {error})"; public static bool operator ==(AsyncSnapshot left, AsyncSnapshot right) { return Equals(left, right); } public static bool operator !=(AsyncSnapshot left, AsyncSnapshot right) { return !Equals(left, right); } public bool Equals(AsyncSnapshot other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return connectionState == other.connectionState && EqualityComparer.Default.Equals(data, other.data) && Equals(error, other.error); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((AsyncSnapshot) obj); } public override int GetHashCode() { unchecked { var hashCode = (int) connectionState; hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(data); hashCode = (hashCode * 397) ^ (error != null ? error.GetHashCode() : 0); return hashCode; } } } public static partial class _async { public delegate Widget AsyncWidgetBuilder(BuildContext context, AsyncSnapshot snapshot); } // TODO(ianh): remove unreachable code above once https://github.com/dart-lang/linter/issues/1139 is fixed public class StreamBuilder : StreamBuilderBase> { public StreamBuilder( _async.AsyncWidgetBuilder builder, Key key = null, T initialData = default, Stream stream = null ) : base(key: key, stream: stream) { D.assert(builder != null); this.builder = builder; this.initialData = initialData; } public readonly _async.AsyncWidgetBuilder builder; public readonly T initialData; public override AsyncSnapshot initial() => AsyncSnapshot.withData(ConnectionState.none, initialData); public override AsyncSnapshot afterConnected(AsyncSnapshot current) => current.inState(ConnectionState.waiting); public override AsyncSnapshot afterData(AsyncSnapshot current, T data) { return AsyncSnapshot.withData(ConnectionState.active, data); } public override AsyncSnapshot afterError(AsyncSnapshot current, object error) { return AsyncSnapshot.withError(ConnectionState.active, error); } public override AsyncSnapshot afterDone(AsyncSnapshot current) => current.inState(ConnectionState.done); public override AsyncSnapshot afterDisconnected(AsyncSnapshot current) => current.inState(ConnectionState.none); public override Widget build(BuildContext context, AsyncSnapshot currentSummary) => builder(context, currentSummary); } // TODO(ianh): remove unreachable code above once https://github.com/dart-lang/linter/issues/1141 is fixed public class FutureBuilder : StatefulWidget { public FutureBuilder( _async.AsyncWidgetBuilder builder, Key key = null, Future future = null, T initialData = default ) : base(key: key) { D.assert(builder != null); this.builder = builder; this.future = future; this.initialData = initialData; } public readonly Future future; public readonly _async.AsyncWidgetBuilder builder; public readonly T initialData; public override State createState() => new _FutureBuilderState(); } class _FutureBuilderState : State> { object _activeCallbackIdentity; AsyncSnapshot _snapshot; public override void initState() { base.initState(); _snapshot = AsyncSnapshot.withData(ConnectionState.none, widget.initialData); _subscribe(); } public override void didUpdateWidget(StatefulWidget statefulWidget) { var oldWidget = statefulWidget as FutureBuilder; if (oldWidget == null) { return; } base.didUpdateWidget(oldWidget); if (oldWidget.future != widget.future) { if (_activeCallbackIdentity != null) { _unsubscribe(); _snapshot = _snapshot.inState(ConnectionState.none); } _subscribe(); } } public override Widget build(BuildContext context) => widget.builder(context, _snapshot); public override void dispose() { _unsubscribe(); base.dispose(); } void _subscribe() { if (widget.future != null) { object callbackIdentity = new object(); _activeCallbackIdentity = callbackIdentity; widget.future.then((object dataIn) => { var data = (T) dataIn; if (_activeCallbackIdentity == callbackIdentity) { setState(() => { _snapshot = AsyncSnapshot.withData(ConnectionState.done, data); }); } }, onError: (Exception error) => { if (_activeCallbackIdentity == callbackIdentity) { setState(() => { _snapshot = AsyncSnapshot.withError(ConnectionState.done, error); }); } return FutureOr.nil; }); _snapshot = _snapshot.inState(ConnectionState.waiting); } } void _unsubscribe() { _activeCallbackIdentity = null; } } }