Using ChangeNotifier to Update Home Widget with Button Press in Flutter

384 Views Asked by At

Here is the substance of my main.dart file:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:webview_flutter/webview_flutter.dart';

Future main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => Home()),
        ChangeNotifierProvider(create: (_) => _HomeState()),
        ChangeNotifierProvider(create: (_) => _SettingsPageState()),
      ],
      child: MaterialApp(
        title: 'My Great App',
        theme: ThemeData(
          primarySwatch: Colors.green,
        ),
        home: Home(),
      ),
    ),
  );
}

class BottomNavBarItemData {
  String label;
  IconData icon;
  BottomNavBarItemData(this.label, this.icon);
}

class PageViewData {
  Widget page;
  PageViewData(this.page);
}

//The Main Page Class
class Home extends StatefulWidget with ChangeNotifier {
  Home({super.key});
  int _selectedIdx = 0;
  int get selectedIdx => _selectedIdx;
  late Widget _body;

  void updateSelectedIndex(index) {
    print('This is _selectedIdx:');
    print(_selectedIdx);
    _selectedIdx = index;
    print('This is now _selectedIdx:');
    print(_selectedIdx);
    print('This is selectedIdx:');
    print(selectedIdx);
    notifyListeners();
  }

  final List<BottomNavBarItemData> navIcons = [
    BottomNavBarItemData("Home", Icons.cottage),
    BottomNavBarItemData("Settings", Icons.settings),
  ];

  final List<PageViewData> pageScreens = [
    PageViewData(const HomePage()),
    PageViewData(SettingsPage()),
    PageViewData(const SettingsPage2()),
  ];

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> with ChangeNotifier {
  final pageTitles = ['My Home', 'My Settings', 'My Settings 2'];

  int get thisSelectedIdx => widget._selectedIdx;

  @override
  void initState() {
    print('Init State for Home');
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    print('This is the top thisSelectedIdx:');
    print(thisSelectedIdx);
    if (thisSelectedIdx == 2) {
      print('The index is now 2!');
      widget._body = IndexedStack(
        index: widget._selectedIdx,
        children: const [
          SettingsPage2(),
        ],
      );
    } else {
      widget._body = IndexedStack(
        index: widget._selectedIdx,
        children: [
          ...widget.pageScreens.map((e) => e.page).toList(),
        ],
      );
    }
    return Scaffold(
      appBar: TopAppBar(appBarTitle: pageTitles[widget._selectedIdx]),
      body: widget._body,
      bottomNavigationBar: BottomNavigationBar(
        type: BottomNavigationBarType.fixed,
        currentIndex: widget._selectedIdx,
        onTap: (idx) => setState(() {
          widget._selectedIdx = idx;
          Home().updateSelectedIndex(widget._selectedIdx);
          print('This is widget._selectedIdx:');
          print(widget._selectedIdx);
          print('This is thisSelectedIdx:');
          print(thisSelectedIdx);
        }),
        items: widget.navIcons
            .map(
              (e) => BottomNavigationBarItem(
                label: e.label,
                icon: Icon(e.icon),
              ),
            )
            .toList(),
        showSelectedLabels: true,
        showUnselectedLabels: true,
        backgroundColor: Colors.black,
        selectedItemColor: const Color.fromARGB(255, 0, 255, 0),
        unselectedItemColor: Colors.grey,
      ),
    );
  }
}

//The Reload Button Widget
class ReloadButton extends StatelessWidget {
  const ReloadButton({super.key});

  @override
  Widget build(BuildContext context) {
    return IconButton(
      color: const Color.fromARGB(255, 0, 255, 0),
      icon: const Icon(Icons.refresh),
      tooltip: 'Refresh the Page',
      onPressed: () {
        print('Page Refreshed');
      },
    );
  }
}

//The App Bar Class
class TopAppBar extends StatefulWidget implements PreferredSizeWidget {
  const TopAppBar(
      {super.key,
      required this.appBarTitle,
      this.preferredSize = const Size.fromHeight(50)});

  final String appBarTitle;
  @override
  final Size preferredSize;

  @override
  State<TopAppBar> createState() => _TopAppBarState();
}

class _TopAppBarState extends State<TopAppBar> {
  @override
  void initState() {
    print('Init State for TopAppBar');
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AppBar(
      foregroundColor: const Color.fromARGB(255, 0, 255, 0),
      title: Text(widget.appBarTitle),
      actions: const <Widget>[
        ReloadButton(), //IconButton
      ], //<Widget>[]
      backgroundColor: Colors.black,
      automaticallyImplyLeading: false,
    );
  }
}

//The Home Page Class
class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late final _controller = WebViewController();
  int indexPosition = 0;
  beginLoading(String A) {
    if (!mounted) return;
    setState(() {
      indexPosition = 1;
    });
  }

  completeLoading(String A) {
    if (!mounted) return;
    setState(() {
      indexPosition = 0;
    });
  }

  @override
  void initState() {
    print('Init State for Home Page');
    _controller
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0x00000000))
      ..clearCache()
      ..setNavigationDelegate(
        NavigationDelegate(
          onPageStarted: (String url) {
            beginLoading(url);
          },
          onPageFinished: (String url) {
            completeLoading(url);
          },
          onWebResourceError: (WebResourceError error) {},
        ),
      )
      ..loadRequest(Uri.parse('https://www.flutter.dev'));
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
    _controller;
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.black,
      padding: const EdgeInsets.only(left: 5.0, right: 5.0),
      child: IndexedStack(
        index: indexPosition,
        children: <Widget>[
          WebViewWidget(controller: _controller),
          Container(
            color: Colors.black,
            child: const Center(child: CircularProgressIndicator()),
          ),
        ],
      ),
    );
  }
}

//The Settings Page Class
class SettingsPage extends StatefulWidget with ChangeNotifier {
  SettingsPage({super.key});

  @override
  State<SettingsPage> createState() => _SettingsPageState();
}

class _SettingsPageState extends State<SettingsPage> with ChangeNotifier {
  bool _thisOptionIsChecked = false;
  bool _thisOption = false;
  String _thisOptionText = 'Off';
  @override
  void initState() {
    print('Init State for Settings Page');
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    Color getColor(Set<MaterialState> states) {
      const Set<MaterialState> interactiveStates = <MaterialState>{
        MaterialState.selected,
        MaterialState.focused,
        MaterialState.pressed,
      };
      if (states.any(interactiveStates.contains)) {
        return Colors.green;
      }
      return Colors.red;
    }

    return LayoutBuilder(
        builder: (BuildContext context, BoxConstraints constraints) {
      return Center(
        child: Container(
          color: Colors.black,
          width: double.infinity,
          height: double.infinity,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              const SizedBox(
                height: 20,
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  SizedBox(
                    width: 180,
                    child: Column(
                      children: const <Widget>[
                        Text(
                          'This Option',
                          textAlign: TextAlign.center,
                          style: TextStyle(
                            color: Colors.white,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ],
                    ),
                  ),
                  SizedBox(
                    width: 50,
                    child: Column(
                      children: <Widget>[
                        Checkbox(
                          checkColor: Colors.white,
                          fillColor:
                              MaterialStateProperty.resolveWith(getColor),
                          value: _thisOptionIsChecked,
                          onChanged: (bool? value) {
                            if (!mounted) return;
                            setState(() {
                              _thisOptionIsChecked = value!;
                              _thisOption = value;
                              if (_thisOption == true) {
                                _thisOptionText = 'On';
                              } else {
                                _thisOptionText = 'Off';
                              }
                            });
                          },
                        ),
                      ],
                    ),
                  ),
                  SizedBox(
                    width: 75,
                    child: Column(
                      children: <Widget>[
                        Text(
                          _thisOptionText,
                          textAlign: TextAlign.center,
                          style: const TextStyle(
                            color: Colors.white,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ],
                    ),
                  ),
                  SizedBox(
                    width: 50,
                    child: Column(
                      children: const <Widget>[
                        Text(''),
                      ],
                    ),
                  ),
                ],
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  SizedBox(
                    height: 35,
                    width: 120,
                    child: TextButton(
                      style: TextButton.styleFrom(
                        textStyle: const TextStyle(
                            fontSize: 11, fontWeight: FontWeight.bold),
                        shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(10.0)),
                        backgroundColor: Colors.grey,
                        foregroundColor: Colors.black,
                      ),
                      onPressed: () {
                        int index = 7;
                        Provider.of<Home>(context, listen: false)
                            .updateSelectedIndex(index);
                        notifyListeners();
                        print('Open Location Settings');
                      },
                      child: const Text('My Settings 2'),
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      );
    });
  }
}

class SettingsPage2 extends StatefulWidget {
  const SettingsPage2({super.key});

  @override
  State<SettingsPage2> createState() => _SettingsPage2State();
}

class _SettingsPage2State extends State<SettingsPage2> {
  late var _controller = WebViewController();
  int indexPosition = 0;
  beginLoading(String A) {
    if (!mounted) return;
    setState(() {
      indexPosition = 1;
    });
  }

  completeLoading(String A) {
    if (!mounted) return;
    setState(() {
      indexPosition = 0;
    });
  }

  @override
  void initState() {
    print('Init State for My Settings Page 2');
    super.initState();
    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setBackgroundColor(const Color(0x00000000))
      ..setNavigationDelegate(
        NavigationDelegate(
          onPageStarted: (String url) {
            beginLoading(url);
          },
          onPageFinished: (String url) {
            completeLoading(url);
          },
          onWebResourceError: (WebResourceError error) {},
        ),
      )
      ..loadRequest(Uri.parse('https://www.google.com'));
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.black,
      padding: const EdgeInsets.only(left: 5.0, right: 5.0),
      child: IndexedStack(
        index: indexPosition,
        children: <Widget>[
          WebViewWidget(
            controller: _controller,
          ),
          Container(
            color: Colors.black,
            child: const Center(child: CircularProgressIndicator()),
          ),
        ],
      ),
    );
  }
}

I only have the two pages, Home and Settings, loading in my navigation menu because I want the second page of settings to remain hidden until the user taps on Settings and then presses the My Settings 2 button.

I am a bit lost using the ChageNotifier provider, which I think is what I need, and I am not sure what else to try. The main page loads with a web page and works great. The Settings page opens and works great as well. On that page, I have a button which, when pressed, should open up another page in the main window. I am using the selectedIdx and _selectedIdx variables to track movement between the pages. When I click on Home or Settings in the bottomNavigationBar, everything prints appropriately. I get the printed output I would expect:

flutter: This is _selectedIdx:
flutter: 0
flutter: This is now _selectedIdx:
flutter: 0
flutter: This is selectedIdx:
flutter: 0
flutter: This is widget._selectedIdx:
flutter: 0
flutter: This is thisSelectedIdx:
flutter: 0
flutter: This is the top thisSelectedIdx:
flutter: 0
flutter: This is _selectedIdx:
flutter: 0
flutter: This is now _selectedIdx:
flutter: 1
flutter: This is selectedIdx:
flutter: 1
flutter: This is widget._selectedIdx:
flutter: 1
flutter: This is thisSelectedIdx:
flutter: 1
flutter: This is the top thisSelectedIdx:
flutter: 1

However, when I click on the My Settings 2 button, it is not behaving the way I would expect it to. It does call the Provider.of<Home>(context, listen: false).updateSelectedIndex(index) function and passed the index of 2, but it just dies there. I get the following:

flutter: This is _selectedIdx:
flutter: 0
flutter: This is now _selectedIdx:
flutter: 2
flutter: This is selectedIdx:
flutter: 2
flutter: Open Settings Page 2

What I would expect to see is this:

flutter: This is _selectedIdx:
flutter: 0
flutter: This is now _selectedIdx:
flutter: 2
flutter: This is selectedIdx:
flutter: 2
flutter: This is widget._selectedIdx:
flutter: 2
flutter: This is thisSelectedIdx:
flutter: 2
flutter: This is the top thisSelectedIdx:
flutter: 2
flutter: Open Settings Page 2

This tells me that the Home class is not being rebuilt. So, even though the function runs, I never get an updated widget for the Home class. I am racking my brain on how to get this button to show the SettingsPage2 class. Any ideas to help get me unstuck? I thought this would be fairly simple to do, but it just doesn't work the way my mind thinks it should.

0

There are 0 best solutions below