Проблема при попытке использовать поставщика с вложенной панелью BottomNavigation для моего состояния - Flutter

Я пытаюсь научиться правильному управлению состоянием с поставщиком, у меня проблема, с которой у меня проблемы. Насколько я могу судить, я все делаю в соответствии с инструкциями из трех отдельных руководств для провайдеров. Я также успешно использую его для своей таблицы данных без проблем. Однако я продолжаю получать сообщения об ошибках и сбоях при попытке использовать поставщика для моего BottomNavigationBar и моего appBar вверх по дереву виджетов.

Я хочу, чтобы моя нижняя панель навигации была отделена от состояния нулевого экрана. Итак, я устанавливаю int с нажатиями кнопок нижней панели навигации в соответствии с индексом, это должно обновить мой индекс GlobalData pageSelection. Затем я обращаюсь к нему вверх по дереву для тернарных операторов. Но я получаю странную ошибку, говоря, что это не в дереве виджетов ...

Я безуспешно пытался сделать то, что указано в предложении об ошибке.

Любая помощь приветствуется!

import 'package:flutter/material.dart';

class GlobalData extends ChangeNotifier {
  int pageSelectionIndex = 0;

  void updatePageSelection(int index) {
    pageSelectionIndex = index;
    notifyListeners();
  }
}

BottomNavBar

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:fp_provider_demo_one/models/global_data.dart';
import 'package:fp_provider_demo_one/screens/balance_screen.dart';
import 'package:fp_provider_demo_one/screens/subtract_screen.dart';
import 'package:fp_provider_demo_one/screens/add_screen.dart';
import 'package:fp_provider_demo_one/screens/inquiry_screen.dart';
import 'package:provider/provider.dart';

// ignore: must_be_immutable
class BottomNavBar extends StatefulWidget {
  @override
  _BottomNavBarState createState() => _BottomNavBarState();
}

class _BottomNavBarState extends State<BottomNavBar> {
  int _currentTabIndex = 0;

  @override
  Widget build(BuildContext context) {
    final _tabPages = <Widget>[
      AddScreen(),
      SubtractScreen(),
      BalanceScreen(),
      InquiryScreen()
    ];

    final _navBarItems = <BottomNavigationBarItem>[
      const BottomNavigationBarItem(
          icon: Icon(Icons.add_circle), label: 'Add'),
      const BottomNavigationBarItem(
          icon: Icon(Icons.remove_circle), label: 'Subtract'),
      const BottomNavigationBarItem(
          icon: Icon(Icons.grid_on_rounded), label: 'Balance'),
      const BottomNavigationBarItem(
          icon: Icon(Icons.show_chart), label: 'Inquiry'),
    ];

    assert(_tabPages.length == _navBarItems.length);
    final navBar = BottomNavigationBar(
      items: _navBarItems,
      showUnselectedLabels: true,
      showSelectedLabels: true,
      unselectedItemColor: Colors.grey,
      selectedItemColor: Colors.orange,
      currentIndex: _currentTabIndex,
      type: BottomNavigationBarType.fixed,
      onTap: (int index) {
        Provider.of<GlobalData>(context).updatePageSelection(index);
        setState(() {
          _currentTabIndex = index;
        });
      },
    );
    return Scaffold(
      body: _tabPages[_currentTabIndex],
      bottomNavigationBar: navBar,
    );
  }
}

Нулевой экран:

///
///
/// Holds bottom nav bar which in turn switches through screens

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:fp_provider_demo_one/models/global_data.dart';
import 'package:fp_provider_demo_one/widgets/bottom_nav_bar.dart';
import 'package:provider/provider.dart';

class ZeroScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    int _selectedIndex = Provider.of<GlobalData>(context).pageSelectionIndex;
    return Scaffold(
      appBar: AppBar(
        title: (_selectedIndex == 0)
            ? Text('Add')
            : (_selectedIndex == 1)
                ? Text('Subtract')
                : (_selectedIndex == 2)
                    ? Text('Balance')
                    : Text('Inquiry'),
        backgroundColor: (_selectedIndex == 0)
            ? Colors.green
            : (_selectedIndex == 1)
                ? Colors.red
                : (_selectedIndex == 2)
                    ? Colors.blue
                    : Colors.orange,
      ),
      bottomNavigationBar: BottomNavBar(),
    );
  }
}

Ошибка:

======== Exception caught by gesture ===============================================================
The following assertion was thrown while handling a gesture:
Tried to listen to a value exposed with provider, from outside of the widget tree.

This is likely caused by an event handler (like a button's onPressed) that called
Provider.of without passing `listen: false`.

To fix, write:
Provider.of<GlobalData>(context, listen: false);

It is unsupported because may pointlessly rebuild the widget associated to the
event handler, when the widget tree doesn't care about the value.

The context used was: BottomNavBar(state: _BottomNavBarState#38ad7)
'package:provider/src/provider.dart':
Failed assertion: line 262 pos 7: 'context.owner.debugBuilding ||
          listen == false ||
          debugIsInInheritedProviderUpdate'

When the exception was thrown, this was the stack: 
#2      Provider.of (package:provider/src/provider.dart:262:7)
#3      _BottomNavBarState.build.<anonymous closure> (package:fp_provider_demo_one/widgets/bottom_nav_bar.dart:49:18)
#4      _BottomNavigationBarState._createTiles.<anonymous closure> (package:flutter/src/material/bottom_navigation_bar.dart:974:26)
#5      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:991:20)
#6      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:182:24)
...
Handler: "onTap"
Recognizer: TapGestureRecognizer#035fc
  debugOwner: GestureDetector
  state: ready
  won arena
  finalPosition: Offset(144.0, 783.0)
  finalLocalPosition: Offset(46.5, 21.5)
  button: 1
  sent tap down
====================================================================================================


person RobbB    schedule 16.03.2021    source источник


Ответы (2)


Вы можете скопировать и вставить полный код ниже
Шаг 1. Используйте Provider.of<GlobalData>(context, listen: false).updatePageSelection

onTap: (int index) {
        Provider.of<GlobalData>(context, listen: false)
            .updatePageSelection(index);

Шаг 2: поместите ChangeNotifierProvider в main()

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => GlobalData(),
      child: MyApp(),
    ),
  );
}

рабочая демонстрация

введите описание изображения здесь

полный код

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

class GlobalData extends ChangeNotifier {
  int pageSelectionIndex = 0;

  void updatePageSelection(int index) {
    pageSelectionIndex = index;
    notifyListeners();
  }
}

class BottomNavBar extends StatefulWidget {
  @override
  _BottomNavBarState createState() => _BottomNavBarState();
}

class _BottomNavBarState extends State<BottomNavBar> {
  int _currentTabIndex = 0;

  @override
  Widget build(BuildContext context) {
    final _tabPages = <Widget>[
      AddScreen(),
      SubtractScreen(),
      BalanceScreen(),
      InquiryScreen()
    ];

    final _navBarItems = <BottomNavigationBarItem>[
      const BottomNavigationBarItem(icon: Icon(Icons.add_circle), label: 'Add'),
      const BottomNavigationBarItem(
          icon: Icon(Icons.remove_circle), label: 'Subtract'),
      const BottomNavigationBarItem(
          icon: Icon(Icons.grid_on_rounded), label: 'Balance'),
      const BottomNavigationBarItem(
          icon: Icon(Icons.show_chart), label: 'Inquiry'),
    ];

    assert(_tabPages.length == _navBarItems.length);
    final navBar = BottomNavigationBar(
      items: _navBarItems,
      showUnselectedLabels: true,
      showSelectedLabels: true,
      unselectedItemColor: Colors.grey,
      selectedItemColor: Colors.orange,
      currentIndex: _currentTabIndex,
      type: BottomNavigationBarType.fixed,
      onTap: (int index) {
        Provider.of<GlobalData>(context, listen: false)
            .updatePageSelection(index);
        setState(() {
          _currentTabIndex = index;
        });
      },
    );
    return Scaffold(
      body: _tabPages[_currentTabIndex],
      bottomNavigationBar: navBar,
    );
  }
}

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => GlobalData(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ZeroScreen(),
    );
  }
}

class ZeroScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    int _selectedIndex = Provider.of<GlobalData>(context).pageSelectionIndex;
    return Scaffold(
      appBar: AppBar(
        title: (_selectedIndex == 0)
            ? Text('Add')
            : (_selectedIndex == 1)
                ? Text('Subtract')
                : (_selectedIndex == 2)
                    ? Text('Balance')
                    : Text('Inquiry'),
        backgroundColor: (_selectedIndex == 0)
            ? Colors.green
            : (_selectedIndex == 1)
                ? Colors.red
                : (_selectedIndex == 2)
                    ? Colors.blue
                    : Colors.orange,
      ),
      bottomNavigationBar: BottomNavBar(),
    );
  }
}

class AddScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(child: Text("AddScreen"));
  }
}

class SubtractScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(child: Text("SubtractScreen"));
  }
}

class BalanceScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(child: Text("BalanceScreen"));
  }
}

class InquiryScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(child: Text("InquiryScreen"));
  }
}
person chunhunghan    schedule 16.03.2021
comment
Это лучший способ. - person kishan dhankecha; 16.03.2021
comment
Это исправило это. Спасибо! Что я здесь делал не так? Было бы здорово узнать об этом, а также найти решение :) - person RobbB; 16.03.2021
comment
@RobbB, для кнопки вы можете увидеть сообщение об ошибке. Вероятно, это вызвано обработчиком событий (например, onPressed кнопки), который вызвал Provider.of без передачи listen: false. - person chunhunghan; 16.03.2021
comment
Полезно знать, спасибо! - person RobbB; 16.03.2021

пробовали ли вы объявить переменную поставщика вне onTap? например:

final provider = Provider.of<GlobalData>(context);
onTap:(index){
provider.updatePageSelection(index);
}

другая возможность состоит в том, что context не может быть достигнута должным образом в onTap, и, как сказано в журнале, вы должны установить listen: false при объявлении поставщика, например:

Provider.of<GlobalData>(context, listen: false);
person Soheil Qorbani    schedule 16.03.2021
comment
Пробовал оба предложения, не повезло. Я получаю следующую ошибку для обоих: Error: Could not find the correct Provider<GlobalData> above this BottomNavBar Widget - person RobbB; 16.03.2021
comment
@RobbB вы предоставляете Провайдера в дереве виджетов, например: ChangeNotifierProvider( create: (_) => GlobalData (), child: Scaffold()) - person Soheil Qorbani; 16.03.2021
comment
Это выше моей головы. Нет, я этого не делаю. - person RobbB; 16.03.2021
comment
@RobbB, не предоставив его, вы не можете получить к нему доступ - person Soheil Qorbani; 16.03.2021
comment
Думаю, я понимаю, о чем вы сейчас говорите. Я попробую это - person RobbB; 16.03.2021