Search code examples
flutterstateinherited-widget

Flutter Inherited Widget - Missing some listener


I'm trying to get the following to work. Changes to the app model state are not picked up via the InheritedWidget 'AppStateProvider'. I've manage to get this working with sinks/streams but was hoping to established a simpler structure.

This is just a test application to switch between various app modes.

What's missing?

import 'package:flutter/material.dart';

void main() {
  runApp(AppStateProvider(
    child: RootPage(),
    appState: new AppState(),
  ));
}

enum AppMode { introduction, login, home }

class AppState {
  AppMode appMode;
  AppState({
    this.appMode = AppMode.introduction,
  });
}

class AppStateProvider extends InheritedWidget {
  final AppState appState;

  AppStateProvider({Key key, Widget child, this.appState})
      : super(key: key, child: child);

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => true;

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

class RootPage extends StatelessWidget {
  AppMode _mode;

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Inherited Widget Test',
      theme: new ThemeData(
        primarySwatch: Colors.blueGrey,
      ),
      home: _body(context),
    );
  }

  Widget _body(BuildContext context) {
    final provider = AppStateProvider.of(context); //Registers as a listener
    final state = provider.appState;
    _mode = state.appMode;

    return new Stack(
      children: <Widget>[
        new Offstage(
          offstage: _mode != AppMode.introduction,
          child: new MaterialApp(
            home: ColorsListPage(
              color: Colors.red,
              targetAppMode: AppMode.login,
              title: "Intro",
            ),
          ),
        ),
        new Offstage(
          offstage: _mode != AppMode.login,
          child: new MaterialApp(
            home: ColorsListPage(
              color: Colors.blue,
              targetAppMode: AppMode.home,
              title: "Login",
            ),
          ),
        ),
        new Offstage(
          offstage: _mode != AppMode.home,
          child: new MaterialApp(
            home: ColorsListPage(
              color: Colors.green,
              targetAppMode: AppMode.introduction,
              title: "Home",
            ),
          ),
        ),
      ],
    );
  }
}

class ColorDetailPage extends StatefulWidget {
  final String title;
  final MaterialColor color;
  final int materialIndex;
  final AppMode targetAppMode;

  ColorDetailPage(
      {this.color, this.title, this.targetAppMode, this.materialIndex: 500});

  @override
  _ColorDetailPageState createState() => new _ColorDetailPageState();
}

class _ColorDetailPageState extends State<ColorDetailPage> {
  @override
  Widget build(BuildContext context) {
    final provider = AppStateProvider.of(context);

    return Scaffold(
      appBar: AppBar(
        backgroundColor: widget.color,
        title: Text(
          '$widget.title[$widget.materialIndex]',
        ),
      ),
      body: Container(
        color: widget.color[widget.materialIndex],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            provider.appState.appMode = widget.targetAppMode;
          });
        },
        heroTag: null,
      ),
    );
  }
}

class ColorsListPage extends StatefulWidget {
  final MaterialColor color;
  final String title;
  final ValueChanged<int> onPush;
  final AppMode targetAppMode;
  final List<int> materialIndices = [
    100,
    200,
    300,
    400,
    500,
    600,
    700,
    800,
    900,
  ];

  ColorsListPage({this.color, this.targetAppMode, this.title, this.onPush});

  @override
  _ColorsListPageState createState() => new _ColorsListPageState();
}

class _ColorsListPageState extends State<ColorsListPage> {
  @override
  Widget build(BuildContext context) {
    final provider = AppStateProvider.of(context);

    return new Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
          backgroundColor: widget.color,
        ),
        body: Container(
          color: Colors.white,
          child: _buildList(context),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              provider.appState.appMode = widget.targetAppMode;
            });
          },
          heroTag: null,
        ));
  }

  Widget _buildList(BuildContext context) {
    return ListView.builder(
      itemCount: widget.materialIndices.length,
      itemBuilder: (BuildContext content, int index) {
        int materialIndex = widget.materialIndices[index];
        return Container(
            color: widget.color[materialIndex],
            child: ListTile(
                title: Text(
                  "$materialIndex",
                  style: TextStyle(fontSize: 24.0),
                ),
                trailing: Icon(Icons.chevron_right),
                onTap: () {
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                        builder: (context) => ColorDetailPage(
                              color: widget.color,
                              title: widget.title,
                              targetAppMode: widget.targetAppMode,
                              materialIndex: materialIndex,
                            )),
                  );
                }
                //onTap: () => onPush(materialIndex),
                ));
      },
    );
  }
}

Solution

  • You need to wrap your InheritedWidget inside a StatefulWidget

    class _AppStateProvider extends InheritedWidget {
      final AppStateProviderState data;
    
      _AppStateProvider({Key key, @required Widget child, @required this.data})
          : super(key: key, child: child);
    
      @override
      bool updateShouldNotify(InheritedWidget oldWidget) => true;
    }
    
    class AppStateProvider extends StatefulWidget {
      final Widget child;
      final AppState appState;
    
      AppStateProvider({
        @required this.child,
        @required this.appState,
      });
    
      static AppStateProviderState of(BuildContext context) {
        return (context.inheritFromWidgetOfExactType(_AppStateProvider)
                as _AppStateProvider)
            .data;
      }
    
      @override
      AppStateProviderState createState() => AppStateProviderState(
            appState,
          );
    }
    
    class AppStateProviderState extends State<AppStateProvider> {
      AppState appState;
    
      AppStateProviderState(this.appState);
    
      void updateAppMode(AppMode appMode) {
        setState(() {
          appState.appMode = appMode;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return _AppStateProvider(
          data: this,
          child: widget.child,
        );
      }
    }
    

    for more information

    pay attention to this method:

      void updateAppMode(AppMode appMode) {
        setState(() {
          appState.appMode = appMode;
        });
      }
    

    you can use it like this:

      floatingActionButton: FloatingActionButton(
        onPressed: () {
          provider.updateAppMode(widget.targetAppMode);
        },
        heroTag: null,
      ),
    

    enter image description here