Search code examples
flutterdartflutter-provider

Provider null after pop


I'm trying to use Provider with MVVM architecture, I have a very strange error with provider, on the main page there are 3 sections, banner, discounts and categories, when I change the quantity of goods in discounts, everything works, but after switching to a category and back, it already gives an error when I change the quantity of goods, the data is not null, I think the problem is with the provider, the scheme is as follows:

discount section-> quantity of goods -> works

home-> category-> go back-> quantity of goods in discounts-> not working

Demo project

The following _CastError was thrown building MainPage(dirty, dependencies: [_InheritedProviderScope<AllGoodsViewModel?>, _LocalizationsScope-[GlobalKey#a9792], MediaQuery, _InheritedProviderScope<MainPageListViewModel?>], state: _MainPageState#eb568):
Null check operator used on a null value

The relevant error-causing widget was: 
  MainPage MainPage:file:///Users/.../lib/main.dart:93:21
When the exception was thrown, this was the stack: 
#0      Element.widget (package:flutter/src/widgets/framework.dart:3229:31)
#1      debugCheckHasMediaQuery.<anonymous closure> (package:flutter/src/widgets/debug.dart:245:17)
#2      debugCheckHasMediaQuery (package:flutter/src/widgets/debug.dart:261:4)
#3      MediaQuery.of (package:flutter/src/widgets/media_query.dart:908:12)
#4      ScreenUtil.screenWidth (package:flutter_screenutil/src/screen_util.dart:148:37)
#5      ScreenUtil.scaleWidth (package:flutter_screenutil/src/screen_util.dart:167:28)
#6      ScreenUtil.setWidth (package:flutter_screenutil/src/screen_util.dart:182:41)
#7      SizeExtension.w (package:flutter_screenutil/src/size_extension.dart:9:32)
#8      _MainPageState.build (package:.../View/MainPage.dart:240:78)
#9      StatefulElement.build (package:flutter/src/widgets/framework.dart:4919:27)
#10     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4806:15)
#11     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4977:11)
#12     Element.rebuild (package:flutter/src/widgets/framework.dart:4529:5)
#13     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2659:19)
#14     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:891:21)
#15     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:370:5)
#16     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1146:15)
#17     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1083:9)
#18     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:997:5)
#22     _invoke (dart:ui/hooks.dart:151:10)
#23     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:308:5)
#24     _drawFrame (dart:ui/hooks.dart:115:31)
(elided 3 frames from dart:async)

ScreenUtilInitService

class ScreenUtilInitService z{
  /// A helper widget that initializes [ScreenUtil]
  ScreenUtilInitService({required this.builder, Key? key,}) : super(key: key);

  final Widget Function(BuildContext) builder;

  @override
  Widget build(BuildContext context) {
    return  ScreenUtilInit(
        designSize: Size(375, 812),
        builder: (context, widget) => builder(context)
    );
  }
}
class MyApp extends StatefulWidget {

  @override
  State<MyApp> createState() => _MyAppState();

}

class _MyAppState extends State<MyApp> {

  bool _initialized = false;
  bool _error = false;
  Widget _present = SplashScreen();

  void initializeFlutterFire() async {
    try {
      if (Platform.isAndroid) {
        await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
      }
      WidgetsFlutterBinding.ensureInitialized();
      await Firebase.initializeApp();
      await message();
      await rootWidget();
      setState(() {
        _initialized = true;
      });
    } catch(e) {
      setState(() {
        _error = true;
      });
    }
  }

  Future<void> rootWidget() async {
    final prefs = await SharedPreferences.getInstance();
    final id = prefs.get("idAddress");
    final logged = prefs.getBool("logged") ?? false;
    if ((FirebaseAuth.instance.currentUser?.uid != null && logged) || id != null) {
      setState(() {
        _present =  MainPage();
      });
      return;
    } else {
      setState(() {
        _present = OfferPage();
      });
      return;
    }
  }

  @override
  void initState() {
    initializeDB();
    initializeFlutterFire();
    super.initState();
    listeners();
  }

  @override
  Widget build(BuildContext context) {
    if(_error) {
      return MaterialApp(home:SplashScreen());
    }
    if (!_initialized) {
      return MaterialApp(home:SplashScreen());
    }
    return RootPage().mainPage(present: _present);
  }
}

and RootPage

class RootPage {
  

  Widget mainPage({Widget? present}){
    return MaterialApp(
        initialRoute: '/',
        routes: {
          '/ProfilePage': (context) => ProfilePage(),
          '/MainPage': (context) => MainPage(),
          '/CartPage': (context) => CartPage(),
        },
        builder: (context, widget) {
          return ScreenUtilInitService(
              builder: (context) => widget!
          );
        },
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MultiProvider(
          providers: [
            ChangeNotifierProvider(
              create: (context) => MainPageListViewModel(),
            ),
            ChangeNotifierProvider(
                create: (context) => CartViewModel(),
                child: CartPage()
            ),
            ChangeNotifierProvider(
              create: (context) => AllGoodsViewModel(),
            ),
            ChangeNotifierProvider(
              create: (context) => GoodsViewModel(),
            ),
          ],
          child: present != null ? present : MainPage(),
        ),
    );
  }

}

MainPage

  @override
  void initState() {
    setting();
    super.initState();
    _scrollListener();
    _notification();
  }

  @override
  void dispose() {
    _scrollController.removeListener(() { });
    _scrollController.dispose();
    NotificationCenter().unsubscribe('cart');
    NotificationCenter().unsubscribe('address');
    super.dispose();
  }

  void setting() async {
    final cart = await SQFliteService.cart.getCount();
    final address = await SQFliteService.location.current();
    setState((){
      _address = address;
      showCart = cart == 0 ? false : true;
    });
    Provider.of<MainPageListViewModel>(context, listen: false).deliveryRequest() ;
    Provider.of<MainPageListViewModel>(context, listen: false).fetchBanner();
    Provider.of<MainPageListViewModel>(context, listen: false).fetchCategory();
    Provider.of<AllGoodsViewModel>(context, listen: false).fetchSale();
    Provider.of<MainPageListViewModel>(context, listen: false).fetchLaunchMessage(context);

  }

  void _scrollListener(){
    _scrollController.addListener(() {

    ///setState is null after pop after category
    //   setState(() {
    //  _bottomOffSet = _scrollController.offset;
    //   });
    });
  }

  void _notification(){

    NotificationCenter().subscribe('cart', () async {
      final result = await SQFliteService.cart.getCount();
      setState(() {
        showCart = result == 0 ? false : true;
        print("update");
      });
    });

    NotificationCenter().subscribe('address', () async {
      final result = await SQFliteService.location.current();
      setState((){
        _address = result;
      });
    });

  }

  @override
  Widget build(BuildContext context) {

    final models = Provider.of<MainPageListViewModel>(context);
    final sale = Provider.of<AllGoodsViewModel>(context);

    final size = MediaQuery.of(context).size;

return Container(
      child: CustomScrollView(
        physics: AlwaysScrollableScrollPhysics(),
        controller: _scrollController,
        slivers: [
          SliverAppBar(
            ...
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate((context, index) => Container(
              color: Colors.transparent,
              child: Column(
                  children: [
                    bannerW(),
                    ViewSale(model: sale.goods),
                    SizedBox(height: 10),
                    ViewCategory(model: models.category)
                  ]
              ),
            ),
                childCount: 1
            ),
          ),
        ],
      ),
    );

ViewSale

class _ViewSaleState extends State<ViewSale> {
  @override
  Widget build(BuildContext context) {
    return Column(
        children: [
          Container(
            margin: EdgeInsets.only(left: 16, right: 16, bottom: 15),
            child: Align(
                alignment: Alignment.centerLeft,
                child: Text("Скидки", style: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.w900, fontSize: 18))),
          ),
          Container(
            height: ((MediaQuery.of(context).size.width/2.4) - 21) + 71,
            width: MediaQuery.of(context).size.width,
            child: ListView.builder(
                padding: EdgeInsets.symmetric(horizontal: 16),
                shrinkWrap: true,
                itemCount: widget.model.length,
                scrollDirection: Axis.horizontal,
                itemBuilder: (item, index) {
                  return ChangeNotifierProvider(
                     create: (context) => AllGoodsViewModel(),
                    child: ViewGoodsSale(model: widget.model[index])
                   );
                }),
          )
        ]
    );
  }
}

ViewSaleGoods

class _ViewGoodsSaleState extends State<ViewGoodsSale> {

  GlobalKey _key = GlobalKey();

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_){
      Provider.of<AllGoodsViewModel>(context, listen: false).setting(widget.model);
    });
  }

  @override
  Widget build(BuildContext context) {

    final model = Provider.of<AllGoodsViewModel>(context);
    final size = MediaQuery.of(context).size;

    Widget inCart(){
    return Container(
      key: _key,
      height: 31,
      child: GestureDetector(
        onPanDown: (details) {
          Goods? item = widget.model;
          RenderBox _cardBox = _key.currentContext!.findRenderObject() as RenderBox;
          final localPosition = details.localPosition;
          final localDx = localPosition.dx;
          if (localDx <= _cardBox.size.width/2) {
            Goods value = cart.firstWhere((element) => element.id == item.id);
            if (item.optState == 0 ? value.orderCount <= 1 : value.orderCount <= value.opt!.count) {
              setState(() {
                context.read<AllGoodsViewModel>().setCountInCart(0);
                final ind = cart.indexWhere((element) => element.id == item.id);
                if (ind != -1) {
                  cart[ind].orderCount = 0;
                  SQFliteService.cart.delete(cart[ind].id);
                  cart.removeAt(ind);
                }
              });
            } else {
              model.haveItem(item: item, operation: item.optState == 0 ? -1 : (-1 * value.opt!.count));
            }
          } else {
            model.haveItem(item: item, operation: item.optState == 0 ? 1 : item.count);
          }
        },
        child: TextButton(
          style: ButtonStyle(
              backgroundColor: MaterialStateProperty.all(Design.appColor),
              padding: MaterialStateProperty.all(EdgeInsets.symmetric(vertical: 8, horizontal: 10)),
              shape: MaterialStateProperty.all(RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(10.0),
              ))
          ),
          onPressed: (){},
          child: Container(
            child: RichText(
              text:  TextSpan(
                text: "",
                children:[
                  WidgetSpan(
                    alignment: PlaceholderAlignment.middle,
                    child: Icon(Icons.remove, size: 14, color: Colors.white),
                  ),
                  TextSpan(
                    text: "  ${widget.model.optState == 0 ? (widget.model.minPrice ?? widget.model.price) : widget.model.opt!.price} ₽  ",
                    style: TextStyle(
                        color: Colors.white,
                        fontSize: 14,
                        fontWeight: FontWeight.w500,
                        fontFamily: "Inter"
                    ),
                  ),
                  WidgetSpan(
                    alignment: PlaceholderAlignment.middle,
                    child: Icon(Icons.add, size: 14, color: Colors.white),
                  )
                ],
              ),
            ),
          ),
        ),
      ),// Your TextButton code goes here.
    );
  }

  Widget noInCart(){
    return Container(
      key: _key,
      height: 31,
      child: TextButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(model.orderBg),
            padding: MaterialStateProperty.all(EdgeInsets.symmetric(vertical: 8, horizontal: 10)),
            shape: MaterialStateProperty.all(RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(10.0),
            ))
        ),
        onPressed: (){
          Goods? item = widget.model;
          model.haveItem(item: item, operation: item.optState == 0 ? 1 : item.count);
        },
        child: Container(
          child: RichText(
            text:  TextSpan(
              text: "${widget.model.optState == 0 ? widget.model.minPrice == null ? widget.model.price : widget.model.minPrice : widget.model.opt!.price} ₽ ",
              style: TextStyle(
                  color: widget.model.minPrice != null ? Design.grey : Colors.black,
                  decoration: widget.model.optState == 0 && widget.model.minPrice != null ? TextDecoration.lineThrough : TextDecoration.none,
                  fontSize: 14,
                  fontWeight: FontWeight.w500,
                  fontFamily: "Inter"

              ),
              children:[
                TextSpan(
                  text: widget.model.minPrice == null ? "" : " ${widget.model.price} ₽",
                  style: TextStyle(
                      color: Colors.black,
                      decoration: TextDecoration.none,
                      fontSize: 14,
                      fontWeight: FontWeight.w500,
                      fontFamily: "Inter"
                  ),
                ),

                WidgetSpan(
                  alignment: PlaceholderAlignment.middle,
                  child: Icon(Icons.add, size: 14, color: Colors.black),
                  style: TextStyle(
                    color: Colors.black,
                    decoration: TextDecoration.none,
                  ),
                )
              ],
            ),
          ),
        ),
      ),
    );
  }


  Widget card({required Size size}) {
    return Container(
      color: Colors.white,
      width: (size.width/2.4) - 11,
      margin: EdgeInsets.only(right: 10),
      child: Column(
        children: [
          Stack(
              children: [
                Container(
                  height: (size.width/2.4) - 21,
                  padding:  EdgeInsets.all(1),
                  decoration: BoxDecoration(
                    image: DecorationImage(
                        fit: BoxFit.contain,
                        image: NetworkImage(widget.model.images.first)
                    ),
                    borderRadius: BorderRadius.all(Radius.circular(10.0)),
                    border: Border.all(
                        width: 1,
                        color: Design.lightGrey
                    ),
                  ),
                ),
                Visibility(
                  visible: context.read<AllGoodsViewModel>().countInCart == 0 ? false : true,
                  child: Container(
                    height: (size.width/2.4) - 21,
                    decoration: BoxDecoration(
                      color: Colors.black.withOpacity(0.5),
                      borderRadius: BorderRadius.all(Radius.circular(10.0)),
                    ),
                    child: Visibility(
                      visible: true,
                      child: Center(
                        child: model.orderCountText,
                      ),
                    ),
                  ),
                )
              ]
          ),
          SizedBox(height: 5),
          Align(
            alignment: Alignment.centerLeft,
            child: Container(
              height: 29,
              child: Text(widget.model.name,
                maxLines: 2,
                style: TextStyle(
                  fontSize: 12,
                  fontWeight: FontWeight.w700,
                  fontFamily: "Inter",
                ),
              ),
            ),
          ),
          SizedBox(height: 6),
          Align(
              alignment: Alignment.centerLeft,
              child: (context.read<AllGoodsViewModel>().countInCart == 0) ? noInCart() : inCart()
          )
        ],
      ),
    );
  }

  return card(size: size);
  }

}

ViewCategory

class _ViewCategoryState extends State<ViewCategory> {

  @override
  Widget build(BuildContext context) {
    return Container(
      child: GridView.builder(
        padding: EdgeInsets.all(16),
        shrinkWrap: true,
          physics: NeverScrollableScrollPhysics(),
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 2,
              childAspectRatio: 1),
        itemCount: widget.model.length,
        itemBuilder: (context, index) {
          return GestureDetector(
              child: Stack(
                children: [
                  Container(
                    decoration: BoxDecoration(
                      color: Design.lightGrey,
                      borderRadius: BorderRadius.all(Radius.circular(10.0)),
                    ),
                      margin: EdgeInsets.only(right: (index.isOdd ? 0 : 5) , left: (index.isOdd ? 5 : 0), bottom: 10 ),
                      child: Image.network(widget.model[index].category?.url ?? "")
                  ),
                  Positioned(
                      left: 10,
                      right: 10,
                      top: 8,
                      child: Text(widget.model[index].category?.name ?? "", style: TextStyle(fontFamily: "Inter", fontWeight: FontWeight.w500, fontSize: 12.sp)),
                  )
                ]
              ),
              onTap: () async {
                if (widget.model[index].category != null) {
                  final data = widget.model[index].category!;
                  final category = CategoryData(id: data.id, name: data.name);
                  if (data.tags == null) {
                    Navigator.of(context).push(
                        MaterialPageRoute(builder: (context) =>
                            ChangeNotifierProvider.value(value: AllGoodsViewModel(),
                              child: AllCategoryGoodsPage(category: category))
                        )
                    );
                  } else {
                    Navigator.of(context).push(
                        MaterialPageRoute(builder: (context) =>
                            ChangeNotifierProvider.value(value: GoodsViewModel(),
                                child: GoodsPage(category: category))
                        )
                    );
                  }
                  FirebaseAnalytics.instance.logEvent(name: "Category", parameters: null);
                }
              }
            );
          },
      ),
    );
  }
}

enter image description here


Solution

  • I solved the problem, the error was not at all in the provider, but in ScreenUtilInitService I changed it to

    class ScreenUtilInitService {
      Widget build(Function(BuildContext) builder) {
        return ScreenUtilInit(
            designSize: Size(375, 812),
            builder: (context, widget) => builder(context)
        );
      }
    }
    

    and removed ScreenUtilInitService from GoodsPage and AllCategoryGoodsPage so ScreenUtilInitService is not redefined as a widget, which resulted in the error