如何在同一个`Expanded`小部件上返回多个`ChangeNotifierProvider`

时间:2020-06-06 13:23:13

标签: flutter dart

我尝试添加网格列表,同时添加第二个网格项目,发现以下错误:

Error: Could not find the correct Provider<Cities> above this PropertiesGrid Widget

To fix, please:

  * Ensure the Provider<Cities> is an ancestor to this PropertiesGrid Widget
  * Provide types to Provider<Cities>
  * Provide types to Consumer<Cities>
  * Provide types to Provider.of<Cities>()
  * Always use package imports. Ex: `import 'package:my_app/my_code.dart';
  * Ensure the correct `context` is being used.

If none of these solutions work, please file a bug at:
https://github.com/rrousselGit/provider/issues

我在下面的代码部分中发现我的Home Screen中的问题:

Expanded(
                          child: FutureBuilder<bool>(
                            future: getData(),
                            builder: (BuildContext context,
                                AsyncSnapshot<bool> snapshot) {
                              if (!snapshot.hasData) {
                                return const SizedBox();
                              } else {
                                return ChangeNotifierProvider(
                                  create: (context) => Properties(),
                                  child: PropertiesGrid(_showOnlyFavorites),
                                );
                              }
                            },
                          ),
                        ),

所以这是我在ProvidersScreensWidgets这种部分中划分的代码,如下图所示:

enter image description here

这是我的主屏幕:

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

import '../providers/properties.dart';
import '../providers/cities.dart';
import '../widgets/properties_grid.dart';
import '../app_theme.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
  int currentTab = 0;
  ScrollController _scrollController = ScrollController();
  bool _showBottomBar = true;

  _scrollListener() {
    if (_scrollController.position.userScrollDirection ==
        ScrollDirection.reverse) {
      setState(() {
        _showBottomBar = false;
      });
    } else if (_scrollController.position.userScrollDirection ==
        ScrollDirection.forward) {
      setState(() {
        _showBottomBar = true;
      });
    }
  }
  var _showOnlyFavorites = false;
  AnimationController animationController;
  bool multiple = true;

  @override
  void initState() {
    animationController = AnimationController(
        duration: const Duration(milliseconds: 2000), vsync: this);
    _scrollController.addListener(_scrollListener);
    super.initState();
  }

  Future<bool> getData() async {
    await Future<dynamic>.delayed(const Duration(milliseconds: 0));
    return true;
  }

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

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 6, // Added
      initialIndex: 0,
      child: Scaffold(
        resizeToAvoidBottomPadding: false,
        extendBody: true,
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: () {},
        ),
        floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
        bottomNavigationBar: AnimatedContainer(
          duration: Duration(milliseconds: 500),
          child: _showBottomBar
              ? BottomAppBar(
                  elevation: 0,
                  shape: CircularNotchedRectangle(),
                  notchMargin: 10,
                  child: Container(
                    height: 60,
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: <Widget>[
                        Row(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: <Widget>[
                            MaterialButton(
                              padding: EdgeInsets.all(0),
                              minWidth: 155,
                              onPressed: () {
                                setState(() {
                                  // currentScreen =
                                  //     Chat(); // if user taps on this dashboard tab will be active
                                  currentTab = 1;
                                });
                              },
                              child: Column(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: <Widget>[
                                  Icon(
                                    Icons.home,
                                    color: currentTab == 1
                                        ? Colors.blue
                                        : Colors.grey,
                                  ),
                                  Text(
                                    'Home',
                                    style: TextStyle(
                                      color: currentTab == 1
                                          ? Colors.blue
                                          : Colors.grey,
                                    ),
                                  ),
                                ],
                              ),
                            )
                          ],
                        ),

                        // Right Tab bar icons

                        Row(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: <Widget>[
                            MaterialButton(
                              padding: EdgeInsets.all(0),
                              minWidth: 60,
                              onPressed: () {
                                setState(() {
                                  // currentScreen =
                                  //     Settings(); // if user taps on this dashboard tab will be active
                                  currentTab = 3;
                                });
                              },
                              child: Column(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: <Widget>[
                                  Icon(
                                    Icons.view_list,
                                    color: currentTab == 3
                                        ? Colors.blue
                                        : Colors.grey,
                                  ),
                                  Text(
                                    'Property List',
                                    style: TextStyle(
                                      color: currentTab == 3
                                          ? Colors.blue
                                          : Colors.grey,
                                    ),
                                  ),
                                ],
                              ),
                            ),
                            MaterialButton(
                              padding: EdgeInsets.all(0),
                              minWidth: 77,
                              onPressed: () {
                                setState(() {
                                  // currentScreen =
                                  //     Settings(); // if user taps on this dashboard tab will be active
                                  currentTab = 4;
                                });
                              },
                              child: Column(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: <Widget>[
                                  Icon(
                                    Icons.location_searching,
                                    color: currentTab == 4
                                        ? Colors.blue
                                        : Colors.grey,
                                  ),
                                  Text(
                                    'Map',
                                    style: TextStyle(
                                      color: currentTab == 4
                                          ? Colors.blue
                                          : Colors.grey,
                                    ),
                                  ),
                                ],
                              ),
                            ),
                          ],
                        )
                      ],
                    ),
                  ),
                )
              : Container(
                  color: Colors.white,
                  width: MediaQuery.of(context).size.width,
                ),
        ),
        backgroundColor: AppTheme.white,
        body: Stack(
          children: <Widget>[
            FutureBuilder<bool>(
              future: getData(),
              builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
                if (!snapshot.hasData) {
                  return const SizedBox();
                } else {
                  return Padding(
                    padding: EdgeInsets.only(
                        top: MediaQuery.of(context).padding.top),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: <Widget>[
                        appBar(),
                        tabBar(),
                        Expanded(
                          child: FutureBuilder<bool>(
                            future: getData(),
                            builder: (BuildContext context,
                                AsyncSnapshot<bool> snapshot) {
                              if (!snapshot.hasData) {
                                return const SizedBox();
                              } else {
                                return ChangeNotifierProvider(
                                  create: (context) => Properties(),
                                  child: PropertiesGrid(_showOnlyFavorites),
                                );
                              }
                            },
                          ),
                        ),
                      ],
                    ),
                  );
                }
              },
            ),
          ],
        ),
      ),
    );
  }

  Widget appBar() {
    return SizedBox(
      height: AppBar().preferredSize.height,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.only(top: 8, left: 8),
            child: Container(
              width: AppBar().preferredSize.height - 8,
              height: AppBar().preferredSize.height - 8,
            ),
          ),
          Expanded(
            child: Center(
              child: Padding(
                padding: const EdgeInsets.only(top: 4),
                child:
                    Image.asset('assets/images/logo.png', fit: BoxFit.contain),
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.only(top: 8, right: 8),
            child: Container(
              width: AppBar().preferredSize.height - 8,
              height: AppBar().preferredSize.height - 8,
              color: Colors.white,
              child: Material(
                color: Colors.transparent,
                child: InkWell(
                  borderRadius:
                      BorderRadius.circular(AppBar().preferredSize.height),
                  child: Icon(
                    Icons.location_on,
                    color: AppTheme.dark_grey,
                  ),
                  onTap: () {
                    setState(() {
                      multiple = !multiple;
                    });
                  },
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget tabBar() {
    return SizedBox(
      height: AppBar().preferredSize.height,
      child: TabBar(
        isScrollable: true,
        unselectedLabelColor: Colors.green,
        labelColor: Colors.blue,
        indicatorColor: Colors.blue,
        labelStyle: TextStyle(fontSize: 15.0,fontStyle: FontStyle.italic),
        tabs: [
          Tab(
            child: Text('All'),
          ),
          Tab(
            child: Text('Office'),
          ),
          Tab(
            child: Text('Commercial'),
          ),
          Tab(
            child: Text('Land'),
          ),
          Tab(
            child: Text('House/Villa'),
          ),
          Tab(
            child: Text('Apartement'),
          ),
        ],
      ),
    );
  }
}

这是Grid小部件的列表:

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

import './province_item.dart';
import './property_item.dart';

import '../providers/cities.dart';
import '../providers/properties.dart';

// import '../providers/properties.dart';

class PropertiesGrid extends StatelessWidget {
  final bool showFavs;

  PropertiesGrid(this.showFavs);

  @override
  Widget build(BuildContext context) {
    final propertiesData = Provider.of<Properties>(context);
    final citiesData = Provider.of<Cities>(context);
    // final productData = Provider.of<Cities>(context);
    final properties = showFavs ? propertiesData.favoriteItems : propertiesData.items;
    final cities = citiesData.items;
    return Container(
      color: Colors.transparent,
      child: SingleChildScrollView(
        child: Column(
          children: [
            Row(
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.all(6.0),
                  child: Align(
                    child: Text(
                      "Show All",
                      style: TextStyle(
                          fontSize: 16,
                          fontStyle: FontStyle.italic,
                          color: Colors.green),
                    ),
                    alignment: Alignment.topLeft,
                  ),
                ),
                Container(
                  margin: const EdgeInsets.only(left: 95.0),
                  child: Padding(
                    padding: const EdgeInsets.all(6.0),
                    child: Align(
                      child: Text(
                        "Popular Properties",
                        style: TextStyle(
                            fontSize: 18,
                            fontStyle: FontStyle.italic,
                            color: Colors.black.withOpacity(0.8)),
                      ),
                      alignment: Alignment.topRight,
                    ),
                  ),
                ),
              ],
            ),
            SizedBox(
              height: 250,
              child: Column(
                children: <Widget>[
                  Expanded(
                    child: GridView.builder(
                      padding: const EdgeInsets.all(10.0),
                      itemCount: properties.length,
                      itemBuilder: (ctx, i) => ChangeNotifierProvider.value(
                        // builder: (c) => products[i],
                        value: properties[i],
                        child: PropertyItem(
                            // products[i].id,
                            // products[i].title,
                            // products[i].imageUrl,
                            ),
                      ),
                      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                        crossAxisCount: 1,
                        childAspectRatio: 3 / 4,
                        crossAxisSpacing: 10,
                        mainAxisSpacing: 10,
                      ),
                      scrollDirection: Axis.horizontal,
                    ),
                  ),
                ],
              ),
            ),
            Row(
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.all(6.0),
                  child: Align(
                    child: Text(
                      "Show All",
                      style: TextStyle(
                          fontSize: 16,
                          fontStyle: FontStyle.italic,
                          color: Colors.green),
                    ),
                    alignment: Alignment.topLeft,
                  ),
                ),
                Container(
                  margin: const EdgeInsets.only(left: 180.0),
                  child: Padding(
                    padding: const EdgeInsets.all(6.0),
                    child: Align(
                      child: Text(
                        "Province",
                        style: TextStyle(
                            fontSize: 18,
                            fontStyle: FontStyle.italic,
                            color: Colors.black.withOpacity(0.8)),
                      ),
                      alignment: Alignment.topRight,
                    ),
                  ),
                ),
              ],
            ),
            SizedBox(
              height: 250,
              child: Column(
                children: <Widget>[
                  Expanded(
                    child: GridView.builder(
                      padding: const EdgeInsets.all(10.0),
                      itemCount: cities.length,
                      itemBuilder: (ctx, i) => ChangeNotifierProvider.value(
                        // builder: (c) => products[i],
                        value: cities[i],
                        child: ProvinceItem(
                            // products[i].id,
                            // products[i].title,
                            // products[i].imageUrl,
                            ),
                      ),
                      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                        crossAxisCount: 1,
                        childAspectRatio: 3 / 4,
                        crossAxisSpacing: 10,
                        mainAxisSpacing: 10,
                      ),
                      scrollDirection: Axis.horizontal,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

这是与小部件相关的省项目:

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

import '../providers/city.dart';

class ProvinceItem extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final city = Provider.of<City>(context, listen: false);
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: InkWell(
        splashColor: Colors.transparent,
        onTap: () => {},
        child: Container(
          decoration: BoxDecoration(
            borderRadius: const BorderRadius.all(Radius.circular(16.0)),
            boxShadow: <BoxShadow>[
              BoxShadow(
                color: Colors.grey.withOpacity(0.6),
                offset: const Offset(2.5, 2.5),
                blurRadius: 16,
              ),
            ],
          ),
          margin: EdgeInsets.all(2),
          child: ClipRRect(
            borderRadius: BorderRadius.only(
              topLeft: Radius.circular(15),
              topRight: Radius.circular(15),
              bottomLeft: Radius.circular(15),
              bottomRight: Radius.circular(15),
            ),
            child: Image.asset(
              city.cityImage,
              fit: BoxFit.cover,
            ),
          ),
        ),
      ),
    );
  }
}

这是City提供者:

import 'package:flutter/material.dart';

class City with ChangeNotifier {
  final String cityId;
  final String cityName;
  final String cityImage;

  City({
    @required this.cityId,
    @required this.cityName,
    @required this.cityImage,
  });
}

并将这个伪数据放入Provider中,就像下面的代码一样:

import '../providers/city.dart';
import 'package:flutter/material.dart';
// import './property.dart';

class Cities with ChangeNotifier {
  List<City> _items = [
    City(
      cityId: 'city1',
      cityName: 'Amman',
      cityImage: 'assets/images/amman.png',
    ),
    City(
      cityId: 'city2',
      cityName: 'Jerash',
      cityImage: 'assets/images/amman.png',
    ),
    City(
      cityId: 'city3',
      cityName: 'Karak',
      cityImage: 'assets/images/amman.png',
    ),
    City(
      cityId: 'city4',
      cityName: 'Aqaba',
      cityImage: 'assets/images/amman.png',
    ),
    City(
      cityId: 'city5',
      cityName: 'Zarqa',
      cityImage: 'assets/images/amman.png',
    ),
    City(
      cityId: 'city6',
      cityName: 'Madaba',
      cityImage: 'assets/images/amman.png',
    ),
    City(
      cityId: 'city7',
      cityName: 'Balqa',
      cityImage: 'assets/images/amman.png',
    ),
    City(
      cityId: 'city8',
      cityName: 'Mafraq',
      cityImage: 'assets/images/amman.png',
    ),
    City(
      cityId: 'city9',
      cityName: 'Maan',
      cityImage: 'assets/images/amman.png',
    ),
    City(
      cityId: 'city10',
      cityName: 'Ajloun',
      cityImage: 'assets/images/amman.png',
    ),
    City(
      cityId: 'city11',
      cityName: 'Irbid',
      cityImage: 'assets/images/amman.png',
    ),
    City(
      cityId: 'city12',
      cityName: 'Tafila',
      cityImage: 'assets/images/amman.png',
    ),
  ];

  List<City> get items {
    return [..._items];
  }
    City findById(String cityId) {
    return _items.firstWhere((cit) => cit.cityId == cityId);
  }
}

很抱歉我添加了很多代码,我试图添加所有需要的信息...

请问是否有任何遗漏的信息,只是让我知道将其添加到问题中。.

已编辑

这是DeBug Banner屏幕:

DeBug Banner

1 个答案:

答案 0 :(得分:1)

哇,那是您发布的很多代码。我必须承认我没有非常彻底地研究它,但是我认为我有问题所在:正如错误消息所指出的那样,您并没有注入类型为Cities 在上下文树中构建PropertiesGrid的点之上。

PropertiesGrid中有以下几行:

final propertiesData = Provider.of<Properties>(context);
final citiesData = Provider.of<Cities>(context);

您正在从上下文树中您上方的位置{em>请求一个Properties实例和一个Cities实例。如果您不满足此要求,提供商将无情地使您崩溃。事实上,您得到了:

(22371): Error: Could not find the correct Provider<Cities> above this PropertiesGrid Widget

所以..让我们看一下您的PropertiesGrid小部件在首页中的位置:

if (!snapshot.hasData) {
  return const SizedBox();
} else {
  return ChangeNotifierProvider(
    create: (context) => Properties(),
    child: PropertiesGrid(_showOnlyFavorites),
  );
}

有一个提供程序可以在Properties上方正确插入一个PropertiesGrid实例,但是没有一个提供Cities实例的提供程序,因此您的小部件具有无法满足的依赖关系

为快速解决方案,建议您使用MultiProvider注入两个依赖项

if (!snapshot.hasData) {
  return const SizedBox();
} else {
  return MultiProvider(
    providers: [
      ChangeNotifierProvider(create: (context) => Properties()),
      ChangeNotifierProvider(create: (context) => Cities()),
    ],
    child: PropertiesGrid(_showOnlyFavorites),
  );
}

请注意,使用MultiProvider并不是强制性的:您可以向这两个实例注入两个提供程序,即使在HomePage树的高度也不一样。只要在 PropertiesGrid之上构建了这两个提供者,您就很好。

不要担心Provider最初似乎很难完全掌握。一旦掌握了它,它便成为一个功能强大的状态管理工具。我建议您看一些online article或教程以进行一些练习。