brianegan / dart_redux_epics Goto Github PK
View Code? Open in Web Editor NEWRedux.dart middleware for handling actions using Dart Streams
License: MIT License
Redux.dart middleware for handling actions using Dart Streams
License: MIT License
Hi,
I might be missing something obvious here, but it seems to me that epics that include timers break tests, even if one explicitly tears down the store.
Here's the simplest possible repro:
void main() {
group('repro', () {
testWidgets('test', (tester) async {
final store = Store<String>(
(state, dynamic action) => state,
initialState: 'Greetings!',
middleware: [
EpicMiddleware(_middleware),
],
);
final widget = StoreProvider<String>(
store: store,
child: MaterialApp(
home: StoreConnector<String, String>(
converter: (s) => s.state,
builder: (context, state) => Text(state),
// At least one dispatch is required, otherwise the epic is never initialized
onInit: (store) => store.dispatch('whatever'),
),
),
);
await tester.pumpWidget(widget);
await store.teardown();
});
});
}
Stream<dynamic> _middleware(Stream<dynamic> actions, EpicStore<String> store) => Observable<dynamic>(actions).debounceTime(aSecond).ignoreElements();
Running this gives:
_AssertionError ('package:flutter_test/src/binding.dart': Failed assertion: line 1050 pos 7: '_currentFakeAsync.nonPeriodicTimerCount == 0': A Timer is still pending even after the widget tree was disposed.)
I considered doing something like this:
Stream<dynamic> _middleware(Stream<dynamic> actions, EpicStore<String> store) =>
Observable<dynamic>(actions)
.debounceTime(aSecond)
.ignoreElements()
.takeUntil<dynamic>(Observable<dynamic>.fromFuture(actions.last));
But it appears as though the actions
stream never completes either (even when tearing down the store).
Am I doing something wrong here?
This line:
https://github.com/brianegan/dart_redux_epics/blob/master/lib/src/epic_middleware.dart#L50
runs timer on every action dispatched within store. In our app it adds visible performance bottleneck. Can you please add option to dispatch actions immediately (can be done backward-compatible)? We could use this option and do not use async*
actions instead.
We are still using 0.7.x version and we will use it for few months from now (dart 2.0 migration). Can you also please publish this option for latest 0.7.x package as well?
Can we get a 1.x release so updating consumers is easier?
I am trying to sync my Firestore with Redux. I am using the following:
redux: "^3.0.0"
flutter_redux: "^0.5.0"
redux_logging: "^0.3.0"
redux_epics: "^0.9.0"
rxdart: "^0.16.7"
When I try to add my epics to the middleware I get this error on this line of code:
final allEpics = combineEpics<AppState>([
ordersEpic,
]);
[dart] The element type '(Stream, EpicStore) → Stream' can't be assigned to the list type '(Stream, EpicStore) → Stream'.
Epic:
Stream<dynamic> ordersEpic(Stream<OrdersOnDataEventAction> actions, EpicStore<AppState> store){
return Observable(actions)
.ofType(TypeToken<RequestOrdersDataEventsAction>())
.switchMap((RequestOrdersDataEventsAction requestOrders){
return getOrders()
.map((orders)=>OrdersOnDataEventAction(orders))
.takeUntil(actions.where((action)=>action is CancelOrdersDataEventsAction));
});
}
Observable<List<DocumentSnapshot>> getOrders(){
return Observable(Firestore.instance.collection("orders").snapshots())
.map((QuerySnapshot q)=>q.documents);
}
Actions:
class RequestOrdersDataEventsAction{}
class CancelOrdersDataEventsAction{}
class OrdersOnDataEventAction{
final orders;
OrdersOnDataEventAction(this.orders);
}
Orders:
final ordersReducer = combineReducers<List<DocumentSnapshot>>([
TypedReducer<List<DocumentSnapshot>,OrdersOnDataEventAction>(_setOrders)
]);
List<DocumentSnapshot> _setOrders(List<DocumentSnapshot> oldOrders, OrdersOnDataEventAction action){
return action.orders;
}
Note I have also tried Stream<dynamic> ordersEpic(Stream<dynamic> actions, EpicStore<AppState> store)
and I get the same error
The element type '(Stream<dynamic>, EpicStore<AppState>) → Stream<dynamic> can't be assigned to the list type '(Stream<dynamic>, EpicStore<AppState>) → Stream<dynamic>
Hi
I have strange problem with async* generator and exceptions handling.
Below code will work only once. I mean if i dispatch LoadDataAction twice, for the first attempt I will get LoadDataFailedAction, but on the second attempt I will get nothing, no exceptions, no _onLoadData call, just silence )
Looks like after exception everything just stopped.
Same methods but with then.catchError work like a charm
Can you help with it?
Epic<AsyncState> getEffects() {
return combineEpics([
new TypedEpic<AsyncState, LoadDataAction>(_onLoadData),
]);
}
Stream<Object> _onLoadData(Stream<LoadDataAction> actions, EpicStore<AsyncState> store) async* {
await for (final action in actions) {
try {
final result = await _loader.load(); // always throw exception
yield new LoadDataSuccessAction(result);
} catch (e) {
yield new LoadDataFailedAction(e.toString());
}
}
}
Hi, first of all thanks for the library!
I'm still new to flutter and I am trying to take some concepts over from Angular (Ngrx) and was stuck on how to create epics that does not dispatch any actions.
In Ngrx you would have to write an effect
with dispatch = false
, I was wondering if I can achieve the same behavior using epics in flutter.
Thanks in advance!
I am using flutter with redux and for handling stream data I'm using redux-epics.
Like this:
Stream<dynamic> getDataEpic(Stream<dynamic> actions, EpicStore<AppState> store) {
return Observable(actions)
.ofType(TypeToken<RequestDataAction>())
.flatMap((RequestDataAction action) {
return getDataStream()
.map(
(data) => UpdateStateWithData(data)
);
})
.takeUntil(actions.where((action) => action is CancelGetDataAction));
}
// getDataStream() - is a stream that returns some stuff...
In my screen in onInit I call store.dispatch(RequestDataAction())
and onDispose I call store.dispatch(CancelGetDataAction())
which destroys the Observable altogether, so the next time I go to this screen if I call store.dispatch(RequestDataAction())
my stream is not sending data, actually the entire Observable is dead!
How can I solve this problem? As far as I can see the problem is takeUntil because I completely closes the observable..
Hi!
Just a small reminder. :D
Is there a technical reason why rxdart is limited to 0.14.x
? Can it be upgraded without a major haul?
We're ok doing the work. I just wanted to check.
I'm using dart_redux_epics, but catcherror can not return error actions
Epics:
Stream<dynamic> loadAccountEpic(Stream<dynamic> actions, EpicStore<AppState> store) {
return new Observable(actions).
ofType(new TypeToken<LoadAccountAction>())
.flatMap((action) {
return new Observable.fromFuture(NetUtils.getInstance()
.userInfo(pb.UserInfoRequest())
.then((res) => LoadAccountCompleteAction(res.user))
.catchError((error) => LoadAccountErrorAction(error)));
});
Actions:
class LoadAccountAction {
LoadAccountAction();
}
class LoadAccountCompleteAction {
User user;
LoadAccountCompleteAction(this.user);
@override
String toString() {
return 'LoadAccountCompleteAction{user: $user}';
}
}
class LoadAccountErrorAction {
final dynamic error;
LoadAccountErrorAction(this.error);
@override
String toString() {
return 'LoadAccountErrorAction{error: $error}';
}
}
Errors:
[VERBOSE-2:ui_dart_state.cc(148)] Unhandled Exception: type 'LoadAccountErrorAction' is not a subtype of type 'FutureOr<LoadAccountCompleteAction>'
Currently it is not possible to consume the state stream, only a current state. It makes it impossible to work with multiple streams.
Hi, at the moment the dependencies specify that it will use Redux up to but not including Redux 3.0.0 and this is causing a problem with the dependency checker in Dart nesting too deep to try to work out a compatible version.
Can we get the version pushed up to include 3.0.0?
FWIW I have the following dependencies:
redux: ^3.0.0
rxdart: ^0.16.5
flutter_redux: ^0.5.0
I think we should use same approach as with redux itself:
_reducer = combineTypedReducers([
new ReducerBinding<State, LoadSuccessAction>(_onLoadSuccess),
new ReducerBinding<State, LoadFailureAction>(_onLoadFailure),
]);
we can create combineTypedEpics
function with similar api:
_effect = combinedTypedEpics([
new EpicBinding<State, LoadRequestAction(_onLoadRequest)
]);
where _onLoadRequest within epic can be defined like this:
Stream<Object> _onLoadRequest(Stream<LoadRequestAction> actions, EpicStore<State> store) {
return actions.asyncMap((action) => api.getData(...).then((data) => new LoadSuccessAction(data)));
}
or even like this:
Stream<Object> _onLoadRequest(Stream<LoadRequestAction> actions, EpicStore<State> store) async* {
await for(final action in actions) {
final data = await api.getData(...);
yield new LoadSuccessAction(data);
}
}
Currently we have to add
where((action) => action is LoadRequestAction)
.map((action) => action as LoadRequestAction)
to most epics.
We cannot use Rx Observables and ofType here because we have to merge stream with its "child" stream because of this #3.
Hi
I have code (just example):
Stream<Object> _onSomeAction(Stream<Object> actions, EpicStore<AppState> store) {
return actions.where((action) => action is SomeAction).asyncMap(
(action) => _api.someAction()
.then((result) => new AnotherAction()),
);
}
It works well if I want to handle AnotherAction only in reducers. But very often I want to handle this action in another Epic(s). For example:
Stream<Object> _onAnotherAction(Stream<Object> actions, EpicStore<AppState> store) {
return actions.where((action) => action is AnotherAction).asyncMap(
(action) => _api.anotherAction()
.then((result) => new FinalAction()),
);
}
It will not work as is. To fix it I can merge streams from both Epics:
Stream<Object> _onSomeAction(Stream<Object> actions, EpicStore<AppState> store) {
final stream = actions.where((action) => action is SomeAction).asyncMap(
(action) => _api.someAction()
.then((result) => new AnotherAction()),
);
return new MergeStream([stream, _onAnotherAction(stream, store)]);
}
But IMHO it complicates the readability of the code and add additional complexity.
I tried to dispatch action directly from Epic:
Stream<Object> _onSomeAction(Stream<Object> actions, EpicStore<AppState> store) {
return actions.where((action) => action is SomeAction).asyncMap(
(action) => _api.someAction()
.then((result) => dispatcher.dispatch(new AnotherAction())), // dispatcher - just a wrapper for store.dispatch
);
}
and it work like a charm! Now I can handle AnotherAction in reducers and in Epics.
But I found bellow comment in EpicStore sources:
/// Due to the way streams are implemented with Dart, it's impossible to
/// perform `store.dispatch` from within an [Epic] or observe the store directly.
if I understood correctly this comment solution with direct dispatch call should not work but it works.
That is why I have few questions:
Thanks!
i have on epic
Stream<dynamic> registerFetchActionEpic(Stream<dynamic> actions, EpicStore<MainState> store) {
return actions.where((action) => action is RegisterFetchAction)
.asyncMap((ac) => Dio().post('http://100.115.92.195:8888/create')
.then((resches) => RegisterFetchSuccessAction())
.catchError((error) => RegisterFetchError(error)));
}
I am trying to combine them with
var eschepischic = combineEpics<MainState>([
registerFetchActionEpic
]);
but
var epicmiddleware = EpicMiddleware(eschepischic)
gives me
The instance member 'eschepischic' can't be accessed in an initializer. (Documentation) Try replacing the reference to the instance member with a different expression
So, because app depends on both redux_epics ^0.10.4 and rxdart ^0.21.0, version solving failed
I was wondering if there was an undocumented ability to inject dependencies into an epic. The obvious example would be for injecting a different api
implementation for testing but I'd also like to configure different endpoints depending on whether I'm running locally or in a live like environment.
The examples in the README hint at the concept of an api - I'm just wondering if there's a way to inject it?
...
.asyncMap((action) =>
// Pseudo api that returns a Future of SearchResults
api.search((action as PerformSearch).searchTerm)
...
Happy to document it if someone can point me in the right direction...
Here is my code:
Stream<dynamic> setEventItemsEffect(
Stream<dynamic> actions, EpicStore<AppState> store) {
return actions
.whereType<SetEventItemAction>()
.asyncMap((action) => handleEventItems(action.task)
.then((res) => UpdateTaskAction(task: action.task)
.catchError((_) => SetEventItemFailAction(task: action.task))
);
}
When to catchError
, this code get error Unhandled Exception: type 'SetEventItemFailAction' is not a subtype of type 'FutureOr<UpdateTaskAction>'
When I change code to this according to #30 :
Stream<dynamic> setEventItemsEffect(
Stream<dynamic> actions, EpicStore<AppState> store) {
return actions
.whereType<SetEventItemAction>()
.asyncMap((action) => Stream<dynamic>.fromFuture(handleEventItems(action.task)
.then((res) => UpdateTaskAction(task: action.task)
.catchError((_) => SetEventItemFailAction(task: action.task)))
);
}
The error is gone, but the type goes like this.
Action: Instance of '_ControllerStream<dynamic>'
or:
Stream<dynamic> setEventItemsEffect(
Stream<dynamic> actions, EpicStore<AppState> store) {
return actions
.whereType<SetEventItemAction>()
.asyncMap((action) => Stream<UpdateTaskAction>.fromFuture(handleEventItems(action.task)
.then((res) => UpdateTaskAction(task: action.task)
.catchError((_) => SetEventItemFailAction(task: action.task)))
);
}
The type goes like this.
Action: Instance of '_ControllerStream<dynamic>'
Doesn't work with latest rxdart v0.24.0
Because core depends on redux_epics ^0.13.0 which depends on rxdart ^0.23.0, rxdart ^0.23.0 is required.
So, because core depends on rxdart ^0.24.0, version solving failed.
pub get failed (1; So, because core depends on rxdart ^0.24.0, version solving failed.)
Is there a pattern to dispatch multiple actions from an Epic?
return Observable(actions)
.ofType(TypeToken<SearchAction>())
.debounce(Duration(milliseconds: 300))
.switchMap((SearchAction action) {
return _getResults(store, action)
.map((result) => OnSearchResult(result))
//want to dispatch a analytics action here something like
// .map((result) => [OnSearchResult(result), SearchAnalyticAction(result)])
.takeUntil(actions.where((action) => action is CancelSearchAction));
is there a way to do that? Or is better to have 2 separeted epics listen same action (SearchAction)?
tks
I get an error using both types.
I don't know if is an error or maybe a don't know how to handle:
In the store initialization
middleware: middleware().addAll([new EpicMiddleware(allEpics)])
or
middleware: middleware().addAll(new EpicMiddleware(allEpics))
or
middleware: [middleware(),new EpicMiddleware(allEpics)]
I don't know how to combine both.
Hi I am using the following code to sync to a Firebase real time database. After opening the Firebase stream, the Epic should send a new CreateTodoListFromStrings
action to the reducers, which keeps the Redux TodoList synced with the database.
I thought it would be enough to just return on Observable (as is shown in examples here https://medium.com/shift-studio/flutter-redux-and-firebase-cloud-firestore-in-sync-2c1accabdac4) and it was resolved into an action by the time it reaches the reducer. Other examples, including this repo's ReadMe, are returning a stream or Observable, so I assumed this pattern would work.
But according to the debugger, the reducers are receiving the Observable itself. As a result none of the reducers are activated, and the store is never updated.
Am I not thinking about this the right way?
hi, will null safety support be added?
Hello! Can you update the package to be compatible with rxdart ^0.26.0 ?
redux_epics 0.14.0 depends on rxdart ^0.24.0 and no versions of redux_epics match >0.14.0 <0.15.0
redux_epics ^0.14.0 and rxdart ^0.26.0, version solving failed
Hi,
I'm using redux_epics with rxDart 0.26.0 and trying to achieve the following:
Making a multiple groups chat app. Need to start streaming chat (firestore collection) on every chat group created. Also cancel the stream when the group is deleted, or on signout.
Stream<dynamic> streamGroupChat(
Stream<dynamic> actions, EpicStore<AppState> store) {
return actions
.where((action) =>
action is LoadGroupSucceededAction && action.group != null)
.flatMap((action) {
return _streamChat(action.group!.id!)
.takeUntil(actions
.where((action) => action is ClearChatsByGroupAction)
.where((newAction) => newAction.groupId == action.group!.id!))
.map((e) {
return LoadChatsSucceededAction(e);
});
});
}
The problem i'm facing is:
When I use flatMap, i get all the chat streams loaded into store...but the takeUntil (Cancellation) does not work.
If I change the flapMap to switchMap, takeUntil starts working but only the last LoadGroupSucceededAction is streamed (overwrites the previous ones).
I thought mergeMap was the key but can't find that in RxDart.
How do I add (flatMap) multiple streams for new groups, replace (switchMap) existing stream for reloaded group, and cancel (takeUntil) streams?
Thanks.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.