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

feat(bloc)!: add support for strongly typed errors on Emitter.onEach/Emitter.forEach #4133

Open
narcodico opened this issue Mar 30, 2024 · 2 comments · May be fixed by #4135
Open

feat(bloc)!: add support for strongly typed errors on Emitter.onEach/Emitter.forEach #4133

narcodico opened this issue Mar 30, 2024 · 2 comments · May be fixed by #4135

Comments

@narcodico
Copy link
Contributor

narcodico commented Mar 30, 2024

Description

The Emitter methods are currently allowing for a standard error handling involving an Object and a StackTrace:
Function(Object error, StackTrace stackTrace).
There's no simple solution at the moment to strongly type your incoming error/exception/failure, in order to make use of onError with your custom type.

Desired Solution

I would like to be able to use onError with my custom type, e.g.:

await emit.onEach(
      _repository.watchData(),
      onData: (data) => add(DataChanged(data)),
      onError: (failure, stackTrace) => add(DataFailed(failure, stackTrace)),
    );

or

await emit.onEach(
      _repository.watchData(),
      onData: (data) => add(DataChanged(data)),
      onError: (failure, _) => add(DataFailed(failure)),
    );

Alternatives Considered

Handling/manipulating the error directly on the incoming stream or in the error handler, both adding too much overhead.

Additional Context

Ideally, the signature would look like onEach<T, E extends Object>.

BREAKING CHANGE: due to signature change, this feature could result in breaking changes in certain situations, like when the data type is explicitly specified. To solve this, the developer would have to either remove the type and rely on type inference, pass Object for the second generic type, or move the type to onData handler, e.g.: onData: (int i) => i.

@narcodico narcodico changed the title feat(bloc): add support for strongly typed errors on Emitter.onEach/Emitter.forEach feat(bloc)!: add support for strongly typed errors on Emitter.onEach/Emitter.forEach Apr 1, 2024
@felangel
Copy link
Owner

felangel commented Apr 1, 2024

Hey @narcodico 👋
Thanks for opening an issue!

It seems a bit risky to do this because if I'm understanding correctly, this change could result in runtime type exceptions since bloc would be casting the object to type E for you.

What would be the benefit of that as opposed to:

await emit.onEach(
  _repository.watchData(),
  onData: (data) => add(DataChanged(data)),
  onError: (failure, stackTrace) => add(DataFailed(failure as MyFailure, stackTrace)),
);

The other issue is onError might be called with errors that are outside of the scope of _repository.watchData() for example, if the underlying stream isn't a broadcast stream and there are multiple subscriptions, onError would be called with a StateError.

As a result, I don't think it's safe to do this because the underlying subscription mechanism operates on Object so I don't think we can always safely make the type more specific without risking runtime TypeErrors.

Let me know what you think and thanks again for taking the time to file this issue! 🙏

@narcodico
Copy link
Contributor Author

Hey @felangel 👋

What would be the benefit of that as opposed to:

await emit.onEach(
  _repository.watchData(),
  onData: (data) => add(DataChanged(data)),
  onError: (failure, stackTrace) => add(DataFailed(failure as MyFailure, stackTrace)),
);

The benefit would be that it would save the developer from manually having to do the cast. It's if the current onEach would not accept a generic data type, but rather would give you a dynamic back and then you'd have to do:
onData: (data) => add(DataChanged(data as MyData)). It's not as clean as having a generic type.

The other issue is onError might be called with errors that are outside of the scope of _repository.watchData() for example, if the underlying stream isn't a broadcast stream and there are multiple subscriptions, onError would be called with a StateError.

I completely agree with this, but at the same time, a developer opting out of the default Object for a custom type would do that because the errors on the stream were handled and converted into a custom failure, e.g.:

Stream<MyData> watchData() => _dataSource.stream.handleError(
        (Object error, StackTrace stackTrace) =>
            throw MyFailure.from(error, stackTrace),
      );

but is also aware of how the stream is being used.
I also feel that the StateError you mentioned about non-broadcast streams being listen to multiple times should not be gracefully handled, since it's an error, not an exception, and should be fixed before releasing.
I can't think of a different error besides this, that could potentially happen after listening to a stream were you handle errors like the one above. Can you?

As a result, I don't think it's safe to do this because the underlying subscription mechanism operates on Object so I don't think we can always safely make the type more specific without risking runtime TypeErrors.

This feature would be great for developers that want to take full control over error handling. I think the usage it's pretty much in the hands of the developer. And the great thing is that not specifying a custom error type would continue operating on Object.

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.

2 participants