How to watch state changes in flutter redux

Issue

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(),
                ),
              ),

Answered By – Taym95

Answer Checked By – Robin (FlutterFixes Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *