Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Whats the motivation for creating ValueWrapper and breaking backwards compatibility with ValueStream? #556

Closed
larssn opened this issue Mar 5, 2021 · 43 comments · Fixed by #560
Milestone

Comments

@larssn
Copy link

larssn commented Mar 5, 2021

ValueWrapper seems to do nothing other than wrap another value, which ValueStream also did.

So why create it? Was it really necessary to break everyone's code due to this seemingly useless class? I'm genuinely curious as to why this was added.

So our code is going from _valueStream.value to _valueStream.valueWrapper.value.

@hoc081098
Copy link
Collaborator

hoc081098 commented Mar 5, 2021

It is necessary when dealing with nullable type.
When using BehaviorSubject<int?>.seeded(null) without ValueWrapper, will crash :)).

Trying import 'rxdart', old code _valueStream.value works again (because we already provided back-compatible extension ValueStreamExtensions)

Sent from my Redmi 7A using FastHub

@larssn
Copy link
Author

larssn commented Mar 5, 2021

When using BehaviorSubject<int?>.seeded(null) without ValueWrapper, will crash.

Trying import 'rxdart', old code _valueStream.value works again (because we provided back-compatible extension)

Sent from my Redmi 7A using FastHub

Why would it ever be necessary to seed a ValueSubject with null?

@hoc081098
Copy link
Collaborator

hoc081098 commented Mar 5, 2021

Many people use seeded(null), see #170 .

And BehaviorSuject.seeded(T), when T is nullable-type, null is valid value.

Sent from my Redmi 7A using FastHub

@larssn
Copy link
Author

larssn commented Mar 5, 2021

Sure, but I don't see how that is related to creating ValueWrapper? Does ValueWrapper fix #170?

I don't see a problem being fixed with ValueWrapper, only moved. Correct me if I'm wrong on that.

@hoc081098
Copy link
Collaborator

hoc081098 commented Mar 5, 2021

Sent from my Redmi 7A using FastHub

@hoc081098 hoc081098 added question waiting for response Waiting for follow up labels Mar 6, 2021
@larssn
Copy link
Author

larssn commented Mar 7, 2021

But it seems to me you're doing a hacking workaround. I'm looking at the code:

T? get value => valueWrapper?.value;

Why is T being forced as nullable here? That doesn't really make sense, as it's nullability status should be set by the invoker, not the library. Maybe the invoker knows that the type is non-nullable; if so, it doesn't make sense that you're forcing it nullable here.

Here's a small example of what I mean:

class SimpleValueStream<T> {
  SimpleValueStream(this.val);
  
  final T val;
}

void main() {
  final test = SimpleValueStream<num?>(null);
  print(test.val); // Works fine

  final test2 = SimpleValueStream<num>(null);
  print(test2.val); // Will fail with: `The argument type 'Null' can't be assigned to the parameter type 'num'.`
}

Now in my simple example above, T will inherits it's nullability from the invoker test.
The second example test2 will fail, because I'm defining the type, and I'm defining it as non-nullable.

And thats my point, the type should be external - nullable or not, and rxdart should not, for even one second, care what the type is. If a test is failing, then rewrite that test. Don't introduce concepts like wrappers whose sole purpose is fixing a test. Fix the test instead. :)

@frankpepermans
Copy link
Member

Yeah we need to roll this back, value should just match the Stream's type, always.

@hoc081098 if seeded(null) on T? Is problematic, maybe we can solve it differently? I'll try to take a look tomorrow.

@hoc081098
Copy link
Collaborator

hoc081098 commented Mar 8, 2021

What should be returned when ValueStream has no data? From prev version, it returns null and i think it is making sence, lib always provides 2 way to get data (valueOrNull and valueOrThrows).

@hoc081098
Copy link
Collaborator

hoc081098 commented Mar 8, 2021

But it seems to me you're doing a hacking workaround. I'm looking at the code:

T? get value => valueWrapper?.value;

Why is T being forced as nullable here? That doesn't really make sense, as it's nullability status should be set by the invoker, not the library. Maybe the invoker knows that the type is non-nullable; if so, it doesn't make sense that you're forcing it nullable here.

Here's a small example of what I mean:

class SimpleValueStream<T> {
  SimpleValueStream(this.val);
  
  final T val;
}

void main() {
  final test = SimpleValueStream<num?>(null);
  print(test.val); // Works fine

  final test2 = SimpleValueStream<num>(null);
  print(test2.val); // Will fail with: `The argument type 'Null' can't be assigned to the parameter type 'num'.`
}

Now in my simple example above, T will inherits it's nullability from the invoker test.
The second example test2 will fail, because I'm defining the type, and I'm defining it as non-nullable.

And thats my point, the type should be external - nullable or not, and rxdart should not, for even one second, care what the type is. If a test is failing, then rewrite that test. Don't introduce concepts like wrappers whose sole purpose is fixing a test. Fix the test instead. :)

You can use ValueStream.requireValue instead of ValueStream.value, it returns a T or throwing error.
Addition, you can check AsyncSnapshot from Flutter, https://github.com/flutter/flutter/blob/021311ed8a2c182d3237330e5d8ae4c4937b3d76/packages/flutter/lib/src/widgets/async.dart#L248, it has two way to get data: T? data and T get requireData

@larssn
Copy link
Author

larssn commented Mar 8, 2021

You can use ValueStream.requireValue instead of ValueStream.value, it returns a T or throwing error.
Addition, you can check AsyncSnapshot from Flutter, https://github.com/flutter/flutter/blob/021311ed8a2c182d3237330e5d8ae4c4937b3d76/packages/flutter/lib/src/widgets/async.dart#L248, it has two way to get data: T? data and T get requireData

But that is not really a clean solution either. As I mentioned above, it should be possible without all these strange workarounds. value is really the only field that should exist.

@frankpepermans
Copy link
Member

frankpepermans commented Mar 8, 2021

What should be returned when ValueStream has no data? From prev version, it returns null and i think it is making sence, lib always provides 2 way to get data (valueOrNull and valueOrThrows).

Yeah that's basically the root of the problem, in fact, value is just problematic by definition, getting a sync value from something that is async.

What if we get rid of the wrapper, and just offer value, now when an emit has not played yet (but maybe was already added, so you effectively are in this problematic 'middle' zone):

  • value has a non-nullable type T
  • no event has passed yet

then: we throw a special Error, e.g. UnsyncedValueError, and perhaps we can even provide a Future<T> with that error, which completes as soon as the value actually emits?

class UnsyncedValueError<T> implements Exception {
  final Future<T> valueFuture;

  String get message =>
      '''A request for ValueStream.value was made, but no value has yet been emitted from the Stream.
  Alternatively, wait for the value to be emitted by using UnsyncedValueError.valueFuture in a try/catch block.''';

  const UnsyncedValueError(this.valueFuture);
}

void main() async {
  final subject = BehaviorSubject<int>();
  
  try {
    print(subject.value);
  } on UnsyncedValueError catch (e) {
    print(await e.valueFuture);
  }
}

but, it is possible that the Subject closes before actually emitting at least one value, so the above Future could once again fail eventually with a kind of RangeError :/

@larssn
Copy link
Author

larssn commented Mar 8, 2021

Let me bring some perspective, I know how it can be difficult to break out of a certain mental mindset.

When seeded, obviously the value is non-nullable, because you supply a value (unless you seed with a nullable type ofc, which is perfectly acceptable as the type is inherited anyway).

If not seeded, and you just use the constructor, the type is forced nullable, as it should be.

It would look something like this:

SimpleBehaviorSubject.seeded(1) -> value's type is int.
SimpleBehaviorSubject<int?>() -> value's type is int?.

Behind the scenes, it would look something like this:

class SimpleBehaviorSubject<T> {
  T _val;
  T get value => _val;
  
  SimpleBehaviorSubject(this._val);

  /// Can be nullable or not, BehaviorSubject is unopinionated about it.
  factory SimpleBehaviorSubject.seeded(T val) {
    return SimpleBehaviorSubject(val);
  }
}

void main() {
  final unseeded = SimpleBehaviorSubject<num?>(null);
  print(unseeded.value);
  
  final seeded = SimpleBehaviorSubject.seeded(1);
  print(seeded.value);
}

Again, the main goal should be to strip all Rxdart bias towards a variable being nullable or not. It should mind it's own business and do what it does best: streams. 😁

@frankpepermans
Copy link
Member

@larssn I don't think it's acceptable to just change to T? when non-seeded :)

The example you provide, also won't compile, as a factory ctr cannot just return a changed Type.

We're just trying to solve the problem of value, that's all, which actually can be seen as late, only you have no real way of knowing exactly when it will be filled.

@larssn
Copy link
Author

larssn commented Mar 8, 2021

The example you provide, also won't compile, as a factory ctr cannot just return a changed Type.

Sorry I'm in Typescript mode most days. I probably should have run it through a compiler first 😇

Anyway, I've updated my example above to something that works. The only quirk is explicitly defining that the unseeded version takes "null" as a param. Not sure if dart can figure out that the type is external, and should not be inferred from within the class itself.

@larssn I don't think it's acceptable to just change to T? when non-seeded :)

You have to! There literally might never be a value. It's the only sound way.

@frankpepermans
Copy link
Member

The example you provide, also won't compile, as a factory ctr cannot just return a changed Type.

Sorry I'm in Typescript mode most days. I probably should have run it through a compiler first 😇

Anyway, I've updated my example above to something that works. The only quirk is explicitly defining that the unseeded version takes "null" as a param. Not sure if dart can figure out that the type is external, and should not be inferred from within the class itself.

@larssn I don't think it's acceptable to just change to T? when non-seeded :)

You have to! There literally might never be a value. It's the only sound way.

The only sound way is to keep the expected Type!

If there never will be a value, and you are trying to access value, then IMO, an error is the only reasonable way forward,
take List for example, list.first will throw if there are no elements, it would be wrong if it yielded null instead.

List.firstWhere on a non-nullable type, doesn't even allow a null return in the orElse handler, because null is not a value which satisfies the List's main Type!

firstWhereOrNull is actually an extension made available via package collections, if you really want something to return and not care for errors, which is actually why @hoc081098 changed it to value and requireValue.

Now, that maybe could be done differently, but please not by just changing the Stream's type to nullable.

@larssn
Copy link
Author

larssn commented Mar 8, 2021

I agree with you: The type should be what the user defines - which is exactly what my revised example above does.

If the programmer using rxdart defines the type to be non-nullable, and then later feeds nulls into the stream, he'll get a crash: Dart already handles that. Rxdart does not need to.

Anyway, before we get off track. My main point opening this thread was simple this: ValueWrapper solves nothing - it only moves a problem that should not be the responsibility of rxdart in the first place. So I questioned it's existence.

@frankpepermans
Copy link
Member

@hoc081098 @larssn see #559

This PR is just a proposal, it brings back value, with the same Type as the parent Stream, but also introduces valueOrNull and hasValue.

It's perhaps not too different from ValueStream, but perhaps more consistent regarding types.

@lukaszciastko
Copy link

lukaszciastko commented Mar 8, 2021

Let me add my two cents: I think creating the ValueStreamExtensions might make it very difficult for newbies to work with RxDart. I know this is not your fault, but IDEs do not suggest "value" as a valid property unless you specifically import RxDart (because it's in an extension). I tend do use value to provide the initialData in StreamBuilders - in views, you most likely don't need to import RxDart (most RxDart code ends up in BloCs/ViewModels, etc.). People with less Rx experience might not know about it, and think: oh, ok, so no more "value". This makes features discovery very difficult.

I'm also a bit frustrated that I need to rewrite all my .value = code to .add().

@frankpepermans
Copy link
Member

@lukaszciastko that's true, I'd prefer just a .value without extension methods too.

Introducing nnbd was no easy feat, and many thanks to @hoc081098 for pushing that forward.
The above mentioned PR brings it back into ValueStream without an extension, but it's just a proposal for now.

@hoc081098
Copy link
Collaborator

I'm also a bit frustrated that I need to rewrite all my .value = code to .add().

#560 will add setter set value(T newValue) for BehaviorSubject

@hoc081098 hoc081098 added this to the 0.27.0 milestone Mar 9, 2021
@jyardin
Copy link

jyardin commented Mar 25, 2021

Let me start by saying say that the work done on this library is awesome, thank you to all the contributors, you make our life with dart streams a lot easier 🙂

Now on this issue: I just stumbled upon this when trying to upgrade to the latest RxDart version. I don't really understand the need to rename the field value to valueOrNull.
The name valueOrNull is redundant with the fact that the field is always nullable, in my opinion.

Even before nnbd, the contract of the ValueStream.value was (implicitly) to either have a value, or a null value if nothing had been emitted yet. It is now (with nndb) easily expressed by the ? decorator.
Of course, there is an ambiguity if the stream can emit null values (if the underlying type is nullable), but it is solved by checking the hasValue field, which tells us if a value has been emitted yet.

I'm not sure I like the new requireValue field also, which is basically just a shortcut for doing value! (but less explicit).

Why not just keep the contract simple with just a T? value and the hasValue, and let the developers check if the stream has value before using it (which he will have to do anyway, even when using the requireValue field)?

@hoc081098
Copy link
Collaborator

requireValue is not the same as valueOrNull!.
Eg.

final s = BehaviorSubject<int?>.seeded(null);
print(s.valueOrNull); // prints `null`
print(s.requireValue); // prints `null`
print(s.valueOrNull!); // crash :))

@jyardin
Copy link

jyardin commented Mar 26, 2021

Yes, it's not the same in case of nullable type, but it makes not much sense to call ! on the value in that case.
But I guess I understand the purpose of requireValue: keep a consistent API whether you are using a nullable or a non-nullable type.
Thanks!

@larssn
Copy link
Author

larssn commented Mar 26, 2021

I still don't understand why rxdart embraces all these kinds of validity checks. Would have left it up to the consumer of the library and kept the fields to a minimum, if it was me. I don't need the library to hold my hand when it comes to type checks.

But I guess I don't have to use them. 🙂

@jyardin
Copy link

jyardin commented Mar 26, 2021

I see you went with renaming requireValue to value in the PR #568. I guess it makes sense with the analogy to List.first and List.firstOrNull, as previously said in this thread (and requireValue was a weird name anyway).
But I think it's a bit dangerous.
Code that was fine before nnbd like this:

final countStream = BehaviorSubject<int>();
print('count is ${countStream.value ?? 'unknown'}');

will now crash without much warning if the BehaviousSubject<int> becomes a BehaviousSubject<int?> after migration.
I know that for such a case, you would have to use valueOrNull now or check if hasValue is true, but it's not obvious when migrating since this was a previously valid (and safe) usage for value.
That's why I would have kept value as always nullable.
If you keep this implementation, I think you need to be extra careful in the migration guide.

edit: messed up the PR reference

@larssn
Copy link
Author

larssn commented Mar 26, 2021

final countStream = BehaviorSubject<int>(); doesn't make sense in an NNDB context. Only final countStream = BehaviorSubject<int>.seeded(-1); would make sense.

Null safety isn't perfect, and this an example of something that is clearly annoying. But the pros outweigh the cons IMO.

@larssn
Copy link
Author

larssn commented Mar 26, 2021

Maybe splitting BehaviorSubject into two separate classes would have been better. There are certain limitations in the dart type system that makes it difficult to implement a version of the same class that is sometimes nullable and sometimes not.

Example:
SeededBehaviorSubject.seeded(1) -> value is never null
BehaviorSubject<int> or BehaviorSubject<int?> -> value is always int?

I'm not fond of that approach either tbh, but it seems implementing this correctly isn't possible, and all the weird "helper" fields that keeps getting added, points to that symptom.

@jyardin
Copy link

jyardin commented Mar 26, 2021

final countStream = BehaviorSubject<int>(); doesn't make sense in an NNDB context. Only final countStream = BehaviorSubject<int>.seeded(-1); would make sense.

Why? I think it makes sense.
You may want to have a BehaviorSubject without a seed. If not, the hasValue property is useless as it would always be true.

@frankpepermans
Copy link
Member

Idd, there's a lot of focus on value, but the main purpose is to listen, starting with the latest value as first event.

Imo, it's perfectly fine to throw a null safe error when accessing value too early, imo the only correct type of value would be FutureOr, but bottom line it's inherently unsafe to trust value, devs should always be careful when relying on it.

@jyardin
Copy link

jyardin commented Mar 26, 2021

Imo, it's perfectly fine to throw a null safe error when accessing value too early, imo the only correct type of value would be FutureOr, but bottom line it's inherently unsafe to trust value, devs should always be careful when relying on it.

The point of NNBD is to give more compile time safety and to avoid null safe error. Dart now gives you language constructs and flow analysis to make sure that doesn't happen.
Throwing exceptions when accessing a value is against the spirit of this in my opinion.
You will end up having to check hasValue every time you access value, which is ok, but safer to do with a nullable value and language construct like ? and flow analysis.

I agree with @larssn that having 2 versions of BehaviorSubject (seeded and unseeded) might be a cleaner solution but it's not realistic I think.

Anyway, as long as there is the valueOrNull property, I'm ok with the current solution.
I just fear you may have a lot of people complaining about unexpected crashes (but I might be wrong).

@frankpepermans
Copy link
Member

List.first is also unsafe, isNotEmpty kinda is like hasValue

@jyardin
Copy link

jyardin commented Mar 26, 2021

I want to clarify that from a design perspective, I agree that having the "nullability" of value being the the same as the type parameter is probably better. Having the value always nullable, like valueOrNull, introduce multiple meanings for null (no value or null value received), which is not ideal.
But from a lib user perspective, you often don't care why the value is null (or absent), you just check it. And nullable constructs gives you a practical and safe way to do this.

I understand the analogy with List.first, but List.first has always thrown, even before NNBD.
I would not have cared has much if value was implemented like this from the start (i.e. throw when !hasValue).
That's why I think it's a risky change.

@larssn
Copy link
Author

larssn commented Mar 26, 2021

final countStream = BehaviorSubject<int>(); doesn't make sense in an NNDB context. Only final countStream = BehaviorSubject<int>.seeded(-1); would make sense.

Why? I think it makes sense.
You may want to have a BehaviorSubject without a seed. If not, the hasValue property is useless as it would always be true.

Let me elaborate. Since we're discussing .value, and not the stream itself, it should be pretty clear that you cannot instantiate a non-nullable value with null. As would be the case if one were to do: BehaviorSubject<int>();
Thats all I'm saying.

Now the STREAM should not emit null unless the type was nullable: <int?>. But that is a trivial problem to solve.
The hard problem is the value field combined with null safety without splitting into multiple classes.

In any case, I think we agree on a fundamental level.

@jyardin
Copy link

jyardin commented Mar 26, 2021

final countStream = BehaviorSubject<int>(); doesn't make sense in an NNDB context. Only final countStream = BehaviorSubject<int>.seeded(-1); would make sense.

Why? I think it makes sense.
You may want to have a BehaviorSubject without a seed. If not, the hasValue property is useless as it would always be true.

Let me elaborate. Since we're discussing .value, and not the stream itself, it should be pretty clear that you cannot instantiate a non-nullable value with null. As would be the case if one were to do: BehaviorSubject<int>();
Thats all I'm saying.

I think it depends how you define value. It does not have to directly represent a value in the stream.
If you define it as Last emitted value, or null if there has been no emission yet (which is exactly how it is defined in the comment in version 0.25), then it is perfectly reasonable to have a null value for value inside a BehaviorSubject<int> (since it does not necessarily have to represent an actual value, but can also represent the absence of value).

@hoc081098
Copy link
Collaborator

That's why I think it's a risky change.

  • I agree, this is a big break-change. But throwing error when using ValueStream.value, which will make it possible for the user to use it properly, make sure we access to the latest value, instead of null and ?? 'Loading...' or something.

  • Like Kotlin (null-safety from first appearance), consider ConflatedBroadcastChannel from kotlinx.coroutines (it is something like BehaviorSubject). This API is similar to the current ValueStream API.

conflated_channel

@jyardin
Copy link

jyardin commented Mar 26, 2021

* I agree, this is a **big** break-change. 

As long as you are aware of this, I'm cool with it 😄

@larssn
Copy link
Author

larssn commented Mar 26, 2021

I think it depends how you define value. It does not have to directly represent a value in the stream.
If you define it as Last emitted value, or null if there has been no emission yet (which is exactly how it is defined in the comment in version 0.25), then it is perfectly reasonable to have a null value for value inside a BehaviorSubject<int> (since it does not necessarily have to represent an actual value, but can also represent the absence of value).

If I'm following then you are close to describing v0.25, with T? as the type for value. But you can't have both, since we're discussing compile time checks here. Whether a stream has emitted or not is irrelevant in this context. It can either be T or T?. Not both - just so we're clear on that.

@hoc081098 I love kotlin, it's great. How would that look in practise? Something like this?

class Test<T> {
  Test([this._value]);
  
  final T? _value;
  T get value => _value!;
}

Because I don't think we need a new exception, as we have one built in if we're trying to force a null value into a non-nullable field.

EDIT:
That just opens up a new can of worms if you were to do:

final t1 = Test<num?>();
print(t1.value); // crash

@jyardin
Copy link

jyardin commented Mar 26, 2021

If I'm following then you are close to describing v0.25, with T? as the type for value. But you can't have both, since we're discussing compile time checks here. Whether a stream has emitted or not is irrelevant in this context. It can either be T or T?. Not both - just so we're clear on that.

I'm not sure I understand the problem 😅.
T? can represent a value in both BehaviorSubject<T> and BehaviorSubject<T?>.
Just like valueOrNull does in the new implementation.
For example, the following compiles and runs fine:

class Wrapper<T> {
  T? value;
  Wrapper([this.value]);
}

void main() {
  var wrapper1 = Wrapper<int>();
  var wrapper2 = Wrapper<int?>();
  
  print(wrapper1.value); // value is of type int? and prints 'null'
  print(wrapper2.value); // value is of type int? and prints 'null'
}

@larssn
Copy link
Author

larssn commented Mar 26, 2021

If I'm following then you are close to describing v0.25, with T? as the type for value. But you can't have both, since we're discussing compile time checks here. Whether a stream has emitted or not is irrelevant in this context. It can either be T or T?. Not both - just so we're clear on that.

I'm not sure I understand the problem 😅.
T? can represent a value in both BehaviorSubject<T> and BehaviorSubject<T?>.
Just like valueOrNull does in the new implementation.
For example, the following compiles and runs fine:

class Wrapper<T> {
  T? value;
  Wrapper([this.value]);
}

void main() {
  var wrapper1 = Wrapper<int>();
  var wrapper2 = Wrapper<int?>();
  
  print(wrapper1.value); // value is of type int? and prints 'null'
  print(wrapper2.value); // value is of type int? and prints 'null'
}

We're trying to solve value being non-nullable in some situations (I think... ARE we still trying to solve that? 😁).

Your example works, but it is what Petrus tried to solve with the ValueWrapper that prompted me to start this thread - namely that value SHOULD BE non-nullable in certain situations (like with a seeded BehaviorSubject).

But it's clearly not possible. All this discussion and we've gotten nowhere except more fields that does little more than cast a value to non-null (something the consumer can do himself).

So actually I'm fine with the simplest of all implementations:
T? get value => _value;

Yes .value is always nullable, but the stream doesn't have to be.

I don't think it's worth the trouble to implement all these "valueOrNull, or requiredValue, etc"-fields. All that work to bypass a simple null check.

@jyardin
Copy link

jyardin commented Mar 26, 2021

We're trying to solve value being non-nullable in some situations (I think... ARE we still trying to solve that? 😁).

Your example works, but it is what Petrus tried to solve with the ValueWrapper that prompted me to start this thread - namely that value SHOULD BE non-nullable in certain situations (like with a seeded BehaviorSubject).

Well OK, that was my counter-point : the value exposed in BehaviourSubject<T> never have to be non-nullable. Whether T is nullable, non-nullable, whether it's seeded or not, you can always store the latest value (or absence of it) in a T?. But I get that in the seeded situation, it is useless overhead.
For what it's worth : the C# implementation of Rx does not allow unseeded BehaviorSubject, so the Value property is obviously not nullable (if the type parameter is not).

But it's clearly not possible. All this discussion and we've gotten nowhere except more fields that does little more than cast a value to non-null (something the consumer can do himself).

So actually I'm fine with the simplest of all implementations:
T? get value => _value;

Yes .value is always nullable, but the stream doesn't have to be.

I don't think it's worth the trouble to implement all these "valueOrNull, or requiredValue, etc"-fields. All that work to bypass a simple null check.

So yes, we agree 😄

@hoc081098
Copy link
Collaborator

hoc081098 commented Mar 27, 2021

If we have only one T get value.

Then, hasValue,

bool hasValue(ValueStream<T> valueStream) {
  try { valueStream.value; return true }
  on ValueStreamError catch(_) { return false; }
}

and valueOrNull

T? valueOrNull(ValueStream<T> valueStream) {
  try { return valueStream.value; }
  on ValueStreamError catch(_) { return null; }
}

We can move it to extensions but that;s old implementation 😃?

@hoc081098
Copy link
Collaborator

hoc081098 commented Mar 27, 2021

@frankpepermans @jyardin @larssn
Just a proposal #570. IMO extension methods are greate, but IDE does not auto-import :( dart-lang/sdk#38894, and only ValueStream.value is needed field (StreamBuilder<T>(initialData: ...))

@dernoun
Copy link

dernoun commented Apr 16, 2021

I am upgrading to the latest version and I get an error on this line There isn’t a setter named 'value' in class 'ValueStreamExtensions'.
final BehaviorSubject<double> aspectR = BehaviorSubject()..value = 16 / 9;
How can I upgrade this ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants