Search code examples
flutterflutter-redux

How to watch state changes in flutter redux


I'm new to Flutter Redux, I got a problem and I have no idea how to deal with it at all! I extracted the main code to keep this simple - tap indicators to switch PageView, scroll PageView to synchronise the indicator. Here is my code:

app state:

class AppState {
  final List menuList;
  final int currentIndex;

  AppState({this.menuList, this.currentIndex});
}

the reducers:

 AppState appReducer(AppState state, Object action) {
  return AppState(
      menuList: menuListReducer(state.menuList, action),
      currentIndex: currentIndexReducer(state.currentIndex, action));
}

final menuListReducer = combineReducers<List>(
    [TypedReducer<List, SetMenuListAction>(_setMenuList)]);

List _setMenuList(List menuList, SetMenuListAction action) {
  menuList = action.menuList;
  return menuList;
}

final currentIndexReducer = combineReducers<int>(
    [TypedReducer<int, SetCurrentIndexAction>(_setCurrentIndex)]);

int _setCurrentIndex(int currentIndex, SetCurrentIndexAction action) {
  currentIndex = action.index;
  return currentIndex;
}

the action:

class SetMenuListAction {
  List menuList;

  SetMenuListAction(this.menuList);
}

class SetCurrentIndexAction {
  int index;

  SetCurrentIndexAction(this.index);
}

the main logic:

void main() {
  final store = Store<AppState>(
    appReducer,
    initialState: AppState(menuList: [
      {
        'picUrl': 'http://pic3.16pic.com/00/55/42/16pic_5542988_b.jpg',
        'description': 'this is the first image'
      },
      {
        'picUrl': 'http://photo.16pic.com/00/38/88/16pic_3888084_b.jpg',
        'description': 'this is the second image'
      },
      {
        'picUrl':
            'http://img4.imgtn.bdimg.com/it/u=3434394339,2114652299&fm=214&gp=0.jpg',
        'description': 'this is the third image'
      },
      {
        'picUrl': 'http://pic1.win4000.com/pic/2/07/8c57e143b1.jpg',
        'description': 'this is the fourth image'
      },
    ], currentIndex: 0),
  );

  runApp(App(
    store: store,
  ));
}

// App
class App extends StatelessWidget {
  final Store<AppState> store;

  const App({Key key, this.store}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return StoreProvider(
      store: store,
      child: MaterialApp(title: 'Flutter redux example', home: MyDetail()),
    );
  }
}

class MyDetail extends StatefulWidget {
  @override
  _MyDetailState createState() => _MyDetailState();
}

class _MyDetailState extends State<MyDetail> with TickerProviderStateMixin {
  PageController _controller;

  @override
  void initState() {
    _controller = PageController(initialPage: 0);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return StoreConnector<AppState, int>(
      converter: (store) => store.state.currentIndex,
      onDidChange: (newIdx) {
        //this won't work because the _controller hasn't been attached to PageView
        _controller.jumpToPage(newIdx);
      },
      builder: (BuildContext context, int idx) {
        return StoreConnector<AppState, List>(
          converter: (store) => store.state.menuList,
          onDidChange: (newList) {
            //maybe do something further
          },
          builder: (BuildContext context, List menus) {
            return Container(
              color: Colors.white,
              child: Column(
                children: <Widget>[
                  //pageview
                  Expanded(
                    child: PageView(
                      children: menus.map((item) {
                        return Column(
                          children: <Widget>[
                            Image.network(item['picUrl']),
                            Text(
                              item['description'],
                              style: TextStyle(fontSize: 24.0),
                            )
                          ],
                        );
                      }).toList(),
                      onPageChanged: (int index) {
                        StoreProvider.of<AppState>(context)
                            .dispatch(SetCurrentIndexAction(index));
                      },
                      physics: BouncingScrollPhysics(),
                    ),
                  ),
                  //indicators
                  Container(
                    margin: EdgeInsets.only(bottom: 50.0),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: menus
                          .asMap()
                          .map((i, item) => MapEntry(
                              i,
                              GestureDetector(
                                onTap: () {
                                  //this won't work either maybe because the widgets is rebuilding
                                  _controller.jumpToPage(i);
                                  StoreProvider.of<AppState>(context)
                                      .dispatch(SetCurrentIndexAction(i));
                                },
                                child: Container(
                                  width: 10.0,
                                  height: 10.0,
                                  color: i == idx
                                      ? Colors.purpleAccent
                                      : Colors.blue,
                                  margin: EdgeInsets.only(right: 10.0),
                                ),
                              )))
                          .values
                          .toList(),
                    ),
                  )
                ],
              ),
            );
          },
        );
      },
    );
  }
}

Sorry for the long code, but I think maybe this can help to understand my problem:

  1. When I tap the indicator, I want to synchronise the PageView, that is _controller.jumpToPage(i), but it will show Errors. So how to make this work?
  2. I can change the currentIndex in another screen, how to synchronise the PageView?
  3. Is there any method to watch the state changes(separately, not the whole state) and do something?

Solution

  • After debugging your code I found that you are missing controller: _controller in PageView, this should fix it:

                  Expanded(
                    child: PageView(
                      controller: _controller,
                      children: menus.map((item) {
                        return Column(
                          children: <Widget>[
                            Image.network(item['picUrl']),
                            Text(
                              item['description'],
                              style: TextStyle(fontSize: 24.0),
                            )
                          ],
                        );
                      }).toList(),
                      onPageChanged: (int index) {
                        StoreProvider.of<AppState>(context)
                            .dispatch(SetCurrentIndexAction(index));
                      },
                      physics: BouncingScrollPhysics(),
                    ),
                  ),