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);
}
}
);
},
),
);
}
}
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