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

StoreConnector for PreferredSizeWidget #23

Closed
bdiegel opened this issue Mar 17, 2018 · 8 comments
Closed

StoreConnector for PreferredSizeWidget #23

bdiegel opened this issue Mar 17, 2018 · 8 comments

Comments

@bdiegel
Copy link

bdiegel commented Mar 17, 2018

Do you have any suggestions for how to to implement a StoreConnector for a PreferredSizeWidget?

Specifically the example I have is AppBar where bottom is a TabBar. The tabs may need to be updated if the app state changes.

@brianegan
Copy link
Owner

Hey there, thanks for writing in :) This is one of the tricky situations! While most of Flutter is Reactive, some parts are imperative: the Navigator, TextEditingController and TabController. Since Redux is Reactive, it can make these situations a bit confusing.

The simplest thing: Would it be possible to call the tabController.animateTo(index) within an onPressed callback, before you dispatch the action?

If not, in this case, using the StoreConnector might not be the best solution. Instead, I could think of a couple options:

  1. If you have one TabBar in the app, create a StatefulWidget. For example: TabScreen. Create a static GlobalKey<TabScreenState>. Create a companion TabScreenState class with a public TabController inside. Then, write a Middleware that listens for the actions that update the state. It would look like this:
void tabNavigationMiddleware(Store<AppState> store, action, NextDispatcher next) {
  // Send the action to your Reducer, it will update the app State
  next(action);

  // Update the TabController if it's an action we care about
  if (action is TabNavigationAction) {
    TabScreen.key.currentState.controller.animateTo(store.state.tabIndex);
  }
}
  1. If you have multiple screens with Tabs, you could dispatch the controller as part of the action, and use that in your Middleware:
class TabNavigationAction {
  final TabController controller;
  final String someOtherData;

  TabNavigationAction(this.controller, this.someOtherData);
}
void tabNavigationMiddleware(Store<AppState> store, action, NextDispatcher next) {
  // Send the action to your Reducer, it will update the app State
  next(action);

  // Update the TabController if it's an action we care about
  if (action is TabNavigationAction) {
    action.controller.animateTo(store.state.tabIndex);
  }
}

Great question! Do any of these options work for ya?

@brianegan
Copy link
Owner

brianegan commented Mar 17, 2018

I think you could also do this with a StoreConnector ... for some reason running a function inside the builder method feels "wrong" because I think the builder should be a pure function, but there's really nothing stopping you from using it this way. It might lead to less gymnastics than doing the Middleware stuff.

This is actually making me wonder whether I should add an onChange callback to the StoreConnector... as a place to interact with Imperative APIs

class MyAppBar extends StatelessWidget implements PreferredSizeWidget {
  final TabController controller;

  MyAppBar({Key key, this.controller}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return new StoreConnector<AppState, int>(
      converter: (store) => store.state.tabIndex,
      builder: (context, tabIndex) {
        controller.animateTo(tabIndex);
        return new AppBar(
          bottom: new TabBar(
            controller: controller,
            tabs: [
              new Tab(icon: new Icon(Icons.directions_car)),
              new Tab(icon: new Icon(Icons.directions_transit)),
              new Tab(icon: new Icon(Icons.directions_bike)),
            ],
          ),
          title: new Text('Tabs Demo'),
        );
      }
    );
  }

  @override
  Size get preferredSize => new Size.fromHeight(kToolbarHeight);
}

@bdiegel
Copy link
Author

bdiegel commented Mar 17, 2018

Thanks for the detailed explanation.

I'm pondering the second technique. My specific problem is not knowing what tabs to display until after loading user preferences and fetching some data from a REST API.

Initially my AppBar.bottom is null. Middleware fetches the necessary content when OnUserPrefsLoadedAction triggers. Once the data is fetched we update the store and want to rebuild the AppBar .

The on-boarding case is similar, with the addition that we first need to gather some information from the user and store it in user preferences. And it is also possible for the user to add/remove tabs.

I am just learning Flutter and have no prior experience with Redux, but this is the approach I have been taking. I am not sure if an imperative style callback would make more sense or not.

@brianegan
Copy link
Owner

Yah, that's an interesting problem for sure :) Overall, sounds like what you've got makes total sense. I think it will get tricky with some of those use-cases, such as adding / removing tabs. In those cases, you might have to listen to the Store directly in a StatefulWidget and rebuild the TabController when changes occur.

@brianegan
Copy link
Owner

Also, would love to hear how you solve it in the end! Thanks again for writing :)

@brianegan
Copy link
Owner

brianegan commented Mar 20, 2018

@bdiegel Hey there! With help from the community, I've published a new version of Redux: 0.3.6 for Dart 1 or 0.4.0 for Dart 2.

This includes a new onWillChange callback that runs before the builder function. It's meant to give folks an "escape hatch" to work with imperative APIs, such as the TabController. Might be interesting to give this technique a shot and see how it works out! Would love any feedback if ya do.

@brianegan
Copy link
Owner

Heya @bdiegel, is it cool if I close this issue? Happy to help more if you'd like me to keep it open, just trying to keep things tidy!

@brianegan
Copy link
Owner

Closing for now. Please let me know if you'd like me to re-open :)

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

2 participants