Giter Club home page Giter Club logo

dart_redux_epics's Introduction

Redux Epics

Travis Build Status

Redux is great for synchronous updates to a store in response to actions. However, working with complex asynchronous operations, such as autocomplete search experiences, can be a bit tricky with traditional middleware. This is where Epics come in!

The best part: Epics are based on Dart Streams. This makes routine tasks easy, and complex tasks such as asynchronous error handling, cancellation, and debouncing a breeze.

Note: For users unfamiliar with Streams, simple async cases are easier to handle with a normal Middleware Function. If normal Middleware Functions or Thunks work for you, you're doing it right! When you find yourself dealing with more complex scenarios, such as writing an Autocomplete UI, check out the Recipes below to see how Streams / Epics can make your life easier.

Example

Let's say your app has a search box. When a user submits a search term, you dispatch a PerformSearchAction which contains the term. In order to actually listen for the PerformSearchAction and make a network request for the results, we can create an Epic!

In this instance, our Epic will need to filter all incoming actions it receives to only the Action it is interested in: the PerformSearchAction. This will be done using the where method on Streams. Then, we need to make a network request with the search term using asyncMap method. Finally, we need to transform those results into an action that contains the search results. If an error has occurred, we'll want to return an error action so our app can respond accordingly.

Here's what the above description looks like in code.

import 'dart:async';
import 'package:redux_epics/redux_epics.dart';

Stream<dynamic> exampleEpic(Stream<dynamic> actions, EpicStore<State> store) {
  return actions
    .where((action) => action is PerformSearchAction)
    .asyncMap((action) => 
      // Pseudo api that returns a Future of SearchResults
      api.search((action as PerformSearch).searchTerm)
        .then((results) => SearchResultsAction(results))
        .catchError((error) => SearchErrorAction(error)));
}

Connecting the Epic to the Redux Store

Now that we've got an epic to work with, we need to wire it up to our Redux store so it can receive a stream of actions. In order to do this, we'll employ the EpicMiddleware.

import 'package:redux_epics/redux_epics.dart';
import 'package:redux/redux.dart';

var epicMiddleware = new EpicMiddleware(exampleEpic);
var store = new Store<State>(fakeReducer, middleware: [epicMiddleware]);

Combining epics and normal middleware

To combine the epic Middleware and normal middleware, simply use both in the list! Note: You may need to provide

var store = new Store<AppState>(
  fakeReducer,
  middleware: [myMiddleware, EpicMiddleware<AppState>(exampleEpic)],
);

If you're combining two Lists, please make sure to use the + or the ... spread operator.

var store = new Store<AppState>(
  fakeReducer,
  middleware: [myMiddleware] + [EpicMiddleware<AppState>(exampleEpic)],
);

Combining Epics

Rather than having one massive Epic that handles every possible type of action, it's best to break Epics down into smaller, more manageable and testable units. This way we could have a searchEpic, a chatEpic, and an updateProfileEpic, for example.

However, the EpicMiddleware accepts only one Epic. So what are we to do? Fear not: redux_epics includes class for combining Epics together!

import 'package:redux_epics/redux_epics.dart';
final epic = combineEpics<State>([
  searchEpic, 
  chatEpic, 
  updateProfileEpic,
]);

Advanced Recipes

In order to perform more advanced operations, it's often helpful to use a library such as RxDart.

Casting

In order to use this library effectively, you generally need filter down to actions of a certain type, such as PerformSearchAction. In the previous examples, you'll noticed that we need to filter using the where method on the Stream, and then manually cast (action as SomeType) later on.

To more conveniently narrow down actions to those of a certain type, you have two options:

TypedEpic

The first option is to use the built-in TypedEpic class. This will allow you to write Epic functions that handle actions of a specific type, rather than all actions!

final epic = new TypedEpic<State, PerformSearchAction>(searchEpic);

Stream<dynamic> searchEpic(
  // Note: This epic only handles PerformSearchActions
  Stream<PerformSearchAction> actions, 
  EpicStore<State> store,
) {
  return actions
    .asyncMap((action) =>
      // No need to cast the action to extract the search term!
      api.search(action.searchTerm)
        .then((results) => SearchResultsAction(results))
        .catchError((error) => SearchErrorAction(error)));
}

RxDart

You can use the whereType method provided by RxDart. It will both perform a where check and then cast the action for you.

import 'package:redux_epics/redux_epics.dart';
import 'package:rxdart/rxdart.dart';

Stream<dynamic> ofTypeEpic(Stream<dynamic> actions, EpicStore<State> store) {
  // Wrap our actions Stream as an Observable. This will enhance the stream with
  // a bit of extra functionality.
  return actions
    // Use `whereType` to narrow down to PerformSearchAction 
    .whereType<PerformSearchAction>()
    .asyncMap((action) =>
      // No need to cast the action to extract the search term!
      api.search(action.searchTerm)
        .then((results) => SearchResultsAction(results))
        .catchError((error) => SearchErrorAction(error)));
}

Cancellation

In certain cases, you may need to cancel an asynchronous task. For example, your app begins loading data in response to a user clicking on a the search button by dispatching a PerformSearchAction, and then the user hit's the back button in order to correct the search term. In that case, your app dispatches a CancelSearchAction. We want our Epic to cancel the previous search in response to the action. So how can we accomplish this?

This is where Observables really shine. In the following example, we'll employ Observables from the RxDart library to beef up the power of streams a bit, using the switchMap and takeUntil operator.

import 'package:redux_epics/redux_epics.dart';
import 'package:rxdart/rxdart.dart';

Stream<dynamic> cancelableSearchEpic(
  Stream<dynamic> actions,
  EpicStore<State> store,
) {
  return actions
      .whereType<PerformSearchAction>()
      // Use SwitchMap. This will ensure if a new PerformSearchAction
      // is dispatched, the previous searchResults will be automatically 
      // discarded.
      //
      // This prevents your app from showing stale results.
      .switchMap((action) {
        return Stream.fromFuture(api.search(action.searchTerm)
            .then((results) => SearchResultsAction(results))
            .catchError((error) => SearchErrorAction(error)))
            // Use takeUntil. This will cancel the search in response to our
            // app dispatching a `CancelSearchAction`.
            .takeUntil(actions.whereType<CancelSearchAction>());
  });
}

Autocomplete using debounce

Let's take this one step further! Say we want to turn our previous example into an Autocomplete Epic. In this case, every time the user types a letter into the Text Input, we want to fetch and show the search results. Each time the user types a letter, we'll dispatch a PerformSearchAction.

In order to prevent making too many API calls, which can cause unnecessary load on your backend servers, we don't want to make an API call on every single PerformSearchAction. Instead, we'll wait until the user pauses typing for a short time before calling the backend API.

We'll achieve this using the debounce operator from RxDart.

import 'package:redux_epics/redux_epics.dart';
import 'package:rxdart/rxdart.dart';

Stream<dynamic> autocompleteEpic(
  Stream<dynamic> actions,
  EpicStore<State> store,
) {
  return actions
      .whereType<PerformSearchAction>()
      // Using debounce will ensure we wait for the user to pause for 
      // 150 milliseconds before making the API call
      .debounce(new Duration(milliseconds: 150))
      .switchMap((action) {
        return Stream.fromFuture(api.search(action.searchTerm)
                .then((results) => SearchResultsAction(results))
                .catchError((error) => SearchErrorAction(error)))
            .takeUntil(actions.whereType<CancelSearchAction>());
  });
}

Dependency Injection

Dependencies can be injected manually with either a Functional or an Object-Oriented style. If you choose, you may use a Dependency Injection or Service locator library as well.

Functional

// epic_file.dart
Epic<AppState> createEpic(WebService service) {
  return (Stream<dynamic> actions, EpicStore<AppState> store) async* {
    service.doSomething()...
  }
}

OO

// epic_file.dart
class MyEpic implements EpicClass<State> {
  final WebService service;

  MyEpic(this.service);

  @override
  Stream<dynamic> call(Stream<dynamic> actions, EpicStore<State> store) {
    service.doSomething()...
  } 
}

Usage - Production

In production code the epics can be created at the point where combineEpics is called. If you're using separate main_<environment>.dart files to configure your application for different environments you may want to pass the config to the RealWebService at this point.

// app_store.dart
import 'package:epic_file.dart';
...

final apiBaseUrl = config.apiBaseUrl

final functionalEpic = createEpic(new RealWebService(apiBaseUrl));
// or
final ooEpic = new MyEpic(new RealWebService(apiBaseUrl));

static final epics = combineEpics<AppState>([
    functionalEpic,
    ooEpic,    
    ...
    ]);
static final epicMiddleware = new EpicMiddleware(epics);

Usage - Testing

...
final testFunctionalEpic = createEpic(new MockWebService());
// or
final testOOEpic = new MyEpic(new MockWebService());
...

dart_redux_epics's People

Contributors

a-abramov avatar bcko avatar brianegan avatar dustin-graham avatar henge9 avatar jaggernod avatar kanelo97 avatar lisenish avatar michaelmarner avatar olehsv avatar subisu avatar tomwscott avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

dart_redux_epics's Issues

How to create an epic that does not dispatch an Action

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!

Unhandled Exception: type 'SetEventItemFailAction' is not a subtype of type 'FutureOr<UpdateTaskAction>'

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>'

Reduce boilerplate code with action type checks and casting

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.

Type mismatch issue

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'.

enter image description here

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>

This issue is also referenced on SO here

Performance issue of Future.delayed hack

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?

Epic `store.dispatch` workflow

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:

  1. What does this comment mean?
  2. Can I continue to use dispatch in Epics without any undesirable consequences?
  3. Why Epics doesn't support this scenario without any MergeStream by default?

Thanks!

takeUntil action make stream unusable the second time

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..

Upgrading rxdart to the latest

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.

Multiple streams with replace and cancel

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.

Error handling in async*

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());
      }
    }
  }

Unhandled Exception: type 'LoadAccountErrorAction' is not a subtype of type 'FutureOr<LoadAccountCompleteAction>'

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>'

Add onChange to the EpicStore

Currently it is not possible to consume the state stream, only a current state. It makes it impossible to work with multiple streams.

How to dispatch multiple actions from an Epic

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

Examples of Dependency Injection in Epics?

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...

Stream, not action, is being sent to reducers

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.
image

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.
image

Am I not thinking about this the right way?

Combining epics and normal middleware

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.

Need update to redux 3.0.0

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

Can't connect redux store

image

This is my epic class.

final githubApi = GithubApi();
  var store = Store<AppState>(
    appReducer,
    initialState: AppState(
      sharedPreferences: initInfo.sharedPreferences,
    ),
    middleware: [
      EpicMiddleware<AppState>(AppEpic(githubApi)),
    ],
  );

image

Can't dispatch the this action.

rxdart 0.26

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

Epics break testing when using timers

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?

Combineepics not working

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

Doesn't work with rxdart v0.24.0

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.)

Release 1.x

Can we get a 1.x release so updating consumers is easier?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.