brianegan / dart_redux_epics

Redux.dart middleware for handling actions using Dart Streams
MIT License
141 stars 22 forks source link

Type mismatch issue #10

Closed shadyaziza closed 6 years ago

shadyaziza commented 6 years ago

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

brianegan commented 6 years ago

Hey hey -- it looks like there might be a couple of different issues tripping you up!

  1. Your intuition to use the Stream<dynamic> ordersEpic(Stream<dynamic> actions, EpicStore<AppState> store) is correct. Since an Epic is defined this way: typedef Stream<dynamic> Epic<State>(Stream<dynamic> actions, EpicStore<State> store);, and that's why you saw the initial type error. However, when you correctly tried to fix this, it appears Dart may be getting confused about the import of the AppState class (that's why it kinda says AppState != AppState). This can generally be solved by: moving the AppState class into it's own file and importing it consistently with the full package-import: import 'package:my_app/app_state.dart'; in all files. Don't worry if this trips ya up -- It's literally the #1 support request I get across my libs, and I wish Dart was smarter about how it handles imports. This can happen if you create and use the AppState class in your main.dart file, and import it into other places, Dart will often run into trouble. Often times this means you have a circular dependency (define a class in main, import main.dart in a reducer.dart file, then import reducer.dart in main.dart).
  2. Your Epic won't quite work. If you look at it, you're starting from a Stream<OrdersOnDataEventAction> actions then trying to narrow this stream down to a Stream of RequestOrdersDataEventsAction using the ofType. Since RequestOrdersDataEventsAction is not a subclass of OrdersOnDataEventAction, you will never reach the switchMap.

I've copied / pasted your code into a new project, all in one main.dart file and this code was working for me without type errors.

Hope this helps! Please let me know if you run into more trouble.

Note: I've created an AppState class + root reducer as I didn't see those above.

// State
class AppState {
  final List<DocumentSnapshot> orders;

  AppState({this.orders = const []});
}

// Epics
final allEpics = combineEpics<AppState>([ordersEpic]);

Stream<dynamic> ordersEpic(
  Stream<dynamic> actions,
  EpicStore<AppState> store,
) {
  return Observable(actions)
      .ofType(TypeToken<RequestOrdersDataEventsAction>())
      .switchMap(
      (RequestOrdersDataEventsAction requestOrders) => 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);
}

// Reducers
AppState stateReducer(AppState prev, dynamic action) {
  return AppState(
    orders: ordersReducer(prev.orders, action),
  );
}

final ordersReducer = combineReducers<List<DocumentSnapshot>>([
  TypedReducer<List<DocumentSnapshot>, OrdersOnDataEventAction>(_setOrders)
]);

List<DocumentSnapshot> _setOrders(
  List<DocumentSnapshot> oldOrders,
  OrdersOnDataEventAction action,
) {
  return action.orders;
}

// Store
final store = Store<AppState>(
  stateReducer,
  initialState: AppState(orders: []),
  middleware: [EpicMiddleware(allEpics)],
);
shadyaziza commented 6 years ago

Thank you so much for the explanitation.

It was not working until I also changed the requestOrders on switchMap to a dynamic type.

I have one more question to make sure I get this right. Now I am getting my list of orders to sync with what I have in the firestoer collection and I view it on a specific page, is there any down side that I am ALWAYS listening on this particular collection on this page, like performance issues. Am I handling this correctly, the desired outcome is that to fetch any updates happen on this collection in real time, is this way the correct one?

brianegan commented 6 years ago

Hrm, that's odd -- using the ofType method from Rx casts requestOrders to a RequestOrdersDataEventsAction for ya, so that should be accurate.

WRT the second question: If you only need to listen to that collection on a certain page, I'd recommend you start listening for Firestore Changes when the screen is initialized and stop listening when the screen is disposed. You could follow the instructions in this post for advice on how to do it: https://medium.com/shift-studio/flutter-redux-and-firebase-cloud-firestore-in-sync-2c1accabdac4

shadyaziza commented 6 years ago

Yes I am using this method already

onInit: (store)=>store.dispatch( RequestOrdersDataEventsAction()),
onDispose: (store)=>store.dispatch( CancelOrdersDataEventsAction()),

And to be honest the application will have this one single screen displayed for a long time so I wanted to double check. Thanks again for the help and keep up the great work :)

brianegan commented 6 years ago

Sure thing! I'll close this one out for now, but please feel free to file another issue if ya run into trouble