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

Add a way to dispatch from StoreConnector #108

Closed
dalewking opened this issue Feb 23, 2019 · 6 comments
Closed

Add a way to dispatch from StoreConnector #108

dalewking opened this issue Feb 23, 2019 · 6 comments

Comments

@dalewking
Copy link

I like the ability to use StoreConnector over StoreBuilder as it lets you get to just what part of the state you need and to filter to only get actual distinct values of that part of the start. The huge drawback with it however is that it doesn't let you dispatch actions because you do not have reference to the store.

So I was thinking it would be very nice to use dispatch easily within a StoreConnector.

One workaround I started using is a separate inherited widget:

class Dispatcher extends InheritedWidget {
  final Store store;

  Dispatcher({Key key, @required this.store, @required Widget child}) : super(key: key, child: child);

  @override
  bool updateShouldNotify(Dispatcher oldWidget) => identical(store, oldWidget.store);

  static Dispatcher of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(Dispatcher);
  }

  Dispatcher dispatch(dynamic action) {
    store.dispatch(action);
    return this;
  }
}

Which you wrap that application widget in and the use it like this:

Dispatcher.of(context).dispatch(someAction);

It works, but seems clunky.

But was thinking it might be nice to have it built into StoreConnector. What I was thinking is that StoreConnector could have 2 builder parameters with different signatures. The new one would be dispatchBuilder(BuildContext, ViewModel, Function(dynamic)) so it would pass a dispatch function to the builder. You could pass one or the other but not both.

Or alternatively just create a new type like StoreConnectorDispatcher. Unfortunately with _StoreStreamListener private I can't really create such a class on my own without duplicating lots of code.

What do you think?

@brianegan
Copy link
Owner

brianegan commented Feb 23, 2019

The huge drawback with it however is that it doesn't let you dispatch actions because you do not have reference to the store.

Hey there -- as a heads up, you can definitely dispatch using a StoreConnector! Remember: You can return anything you need from the StoreConnector: Just some of the information ya need, just the dispatch function (I wouldn't personally do this), a function that dispatches internally, or a "ViewModel" class that contains some data and some methods that dispatch actions.

A couple of options:

/// Just returns a function that takes in an integer and dispatches an `ItemSelectedAction`
StoreConnector<AppState, void Function(int selectedItemId)>(
  converter: (store) => (int selectedItemId) => store.dispatch(ItemSelectedAction(selectedItemId)),
  builder: (context, onItemSelected) => RaisedButton(
    child: Text("Press me!"),
    onPressed: () => onItemSelected(1),
  ),
);


/// More complex example with a "ViewModel"
class _ViewModel {
  final int itemId;
  final void Function() onItemSelected;

  _ViewModel(this.itemId, this.onItemSelected);
}

StoreConnector<AppState, _ViewModel>(
  converter: (store) {
    final itemId = store.state.getMeAnItemId;
    
    return _ViewModel(
      itemId,
      () => store.dispatch(ItemSelectedAction(itemId)),
    );
  },
  builder: (context, viewModel) => RaisedButton(
    child: Text("Displaying ${viewModel.itemId}"),
    onPressed: viewModel.onItemSelected,
  ),
);

What do you think of this approach?

@dawidhyzy
Copy link

I was recently playing with that and found ViewModel option very useful and clean it also helped me with testability so I can decouple view and Store and pass mocked ViewModel into Page/Screen content under test.
BTW. @brianegan do you consider adding some test examples to the repo? I failed to mock Store. I guess more people have the same issue.

@brianegan
Copy link
Owner

@dawidhyzy Yes, that's a fair point! I've got tests in a couple of other samples, but not directly in this lib.

@dalewking Did this help solve the issue?

@dawidhyzy
Copy link

@brianegan could you please share a link where I can see Store mocking?

@brianegan
Copy link
Owner

brianegan commented Mar 4, 2019

Hey @dawidhyzy -- sorry, missed that last comment! When it comes to mocking, it depends on the situation!

For example, you can use the mockito package to mock the store. This is great for verifying interactions with the store.

class MockStore extends Mock implements Store<AppState> {}

void main() {
    test('middleware should dispatch a loading success action', () {
      final store = MockStore();
      void myMiddleware(Store<AppState> store, action, next) {
        if (action is FetchTodos) {
           Future.value([Todo('A')]).then((todos) => store.dispatch(FetchTodosCompleted(todos)));
        }
      }

      myMiddleware(mockStore, FetchTodos(), (_) {});

      verify(store.dispatch(argThat(TypeMatcher<FetchTodosAction>())));
    });
}

If you need to take more control of the Store, you can always create your own Mock implementation by implementing the Store interface!

class MockStore implements Store<AppState> {
  List<dynamic> dispatchedActions;

  @override
  void dispatch(dynamic action) {
    dispatchedActions.add(action);
   }

   // Rest of the implementation ...
}

@brianegan
Copy link
Owner

Gonna close this out since I think the original Q was answered. Please feel free to open new issues for other topics!

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

No branches or pull requests

3 participants