I am making a simple shopApp where I implemented the firebase Authentication, AutoLogout and AutoLogin functionality.
The error I am facing is if I restart the app there is no error but once I click on logout button the red screen occurs showing (Null check operator used on a null value).
Here is my main.dart
file
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: Auth(),
),
ChangeNotifierProxyProvider<Auth, ProductsProvider>(
create: (ctxx) => ProductsProvider('', '', []),
update: (ctx, auth, previousProduct) => ProductsProvider(
auth.token,
auth.userId,
previousProduct == null ? [] : previousProduct.items,
),
),
ChangeNotifierProvider.value(
value: Cart(),
),
ChangeNotifierProxyProvider<Auth, Order>(
create: (ctxx) => Order('', '', []),
update: (ctx, auth, previousProduct) => Order(
auth.token,
auth.userId,
previousProduct == null ? [] : previousProduct.orders),
),
],
child: Consumer<Auth>(
builder: (ctx, auth, _) => MaterialApp(
title: 'Shop App',
theme: ThemeData(
primaryColor: Colors.grey,
colorScheme:
ColorScheme.fromSwatch().copyWith(secondary: Colors.red),
fontFamily: 'Lato',
),
home: auth.isAuth
? const ProductOverviewScreen()
: FutureBuilder(
future: auth.tryAutoLogin(),
builder: (ctxx, snapshot) =>
snapshot.connectionState == ConnectionState.waiting
? const SplashScreen()
: const AuthScreen(),
),
// initialRoute: '/',
routes: {
ProductDescriptionScreen.routName: (context) =>
const ProductDescriptionScreen(),
CartScreen.routName: ((context) => const CartScreen()),
OrderScreen.orderscreen: (context) => const OrderScreen(),
UserProductScreen.productitemrout: (context) =>
const UserProductScreen(),
EditProductScreen.routname: (context) =>
const EditProductScreen(),
},
),
));
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Shop App'),
),
body: const Center(child: Text("My Shop App")),
);
}
}
Here is my authentication file
class Auth with ChangeNotifier {
String? _token;
DateTime? _expiryDate;
String? _userId;
Timer? _authTimer;
bool get isAuth {
// return token != null;
if (_token == null) {
return false;
}
return true;
}
String get token {
if (_expiryDate != null &&
_expiryDate!.isAfter(DateTime.now()) &&
_token != null) {
return _token as String;
}
return null as String;
}
String get userId {
return _userId!;
}
Future<void> userSignUp(String email, String password) async {
/* final responce =*/ await http.post(
Uri.parse(
'https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=AIzaSyDawXmKNAd-O4EqFGNZWgupIGV-TUWW0Qs'),
body: json.encode(
{
'email': email,
'password': password,
'returnSecureToken': true,
},
),
);
}
Future<void> userSignIn(String email, String password) async {
try {
final responce = await http.post(
Uri.parse(
'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=AIzaSyDawXmKNAd-O4EqFGNZWgupIGV-TUWW0Qs'),
body: json.encode(
{
'email': email,
'password': password,
'returnSecureToken': true,
},
),
);
final Responce = json.decode(responce.body);
if (Responce['error'] != null) {
throw HttpException(Responce['error']['message']);
}
_token = Responce['idToken'];
_userId = Responce['localId'];
_expiryDate = DateTime.now().add(
Duration(
seconds: int.parse(
Responce['expiresIn'],
),
),
);
_autoLogOut();
notifyListeners();
final prefs = await SharedPreferences.getInstance();
final userDate = json.encode(
{
'token': _token,
'userId': _userId,
'expiryDate': _expiryDate!.toIso8601String(),
},
);
prefs.setString('userData', userDate);
} catch (error) {
rethrow;
}
}
Future<bool> tryAutoLogin() async {
final prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey('userDate')) {
return false;
}
final extractDate =
json.decode(prefs.getString('userDate')!) as Map<String, Object>;
final expiryDate = DateTime.parse(extractDate['expiryDate'] as String);
if (expiryDate.isAfter(DateTime.now())) {
return false;
}
_token = extractDate['token'] as String;
_userId = extractDate['userId'] as String;
_expiryDate = expiryDate;
notifyListeners();
_autoLogOut();
return true;
}
Future<void> logOut() async {
_token = null;
_userId = null;
_expiryDate = null;
if (_authTimer != null) {
_authTimer!.cancel();
}
notifyListeners();
final prefs = await SharedPreferences.getInstance();
prefs.clear();
}
void _autoLogOut() {
if (_authTimer != null) {
_authTimer!.cancel();
_authTimer = null;
}
final timeToExpire = _expiryDate!.difference(DateTime.now()).inSeconds;
_authTimer = Timer(Duration(seconds: timeToExpire), logOut);
}
}
Here is my AppDrawer Class file
class AppDrawer extends StatelessWidget {
const AppDrawer({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Drawer(
child: Column(
children: <Widget>[
AppBar(
title: const Text("Hello Friend"),
automaticallyImplyLeading: false,
),
const Divider(),
ListTile(
leading: const Icon(
Icons.shop,
),
title: const Text("Shop"),
onTap: () {
Navigator.of(context).pushReplacementNamed('/');
},
),
const Divider(),
ListTile(
leading: const Icon(Icons.payment),
title: const Text("Orders"),
onTap: () {
Navigator.of(context)
.pushReplacementNamed(OrderScreen.orderscreen);
},
),
const Divider(),
ListTile(
leading: const Icon(Icons.edit),
title: const Text("Manage Products"),
onTap: () {
Navigator.of(context)
.pushReplacementNamed(UserProductScreen.productitemrout);
},
),
const Divider(),
ListTile(
leading: const Icon(Icons.exit_to_app),
title: const Text("LogOut"),
onTap: () {
Navigator.of(context).pop();
Provider.of<Auth>(context, listen: false).logOut();
// Navigator.of(context).pushReplacementNamed('/');
},
),
],
),
);
}
}
Here is my Product_itme class file
class ProductItem extends StatelessWidget {
const ProductItem({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final product = Provider.of<Product>(context);
final cart = Provider.of<Cart>(context);
final authData = Provider.of<Auth>(context, listen: false);
return ClipRRect(
borderRadius: BorderRadius.circular(15),
child: GridTile(
footer: GridTileBar(
backgroundColor: Colors.black87,
leading: IconButton(
icon:
Icon(product.isFav ? Icons.favorite : Icons.favorite_border),
color: Theme.of(context).colorScheme.secondary,
onPressed: () {
product.toggleFavStatus(authData.token, authData.userId);
},
),
title: Text(product.title),
trailing: IconButton(
icon: const Icon(Icons.shopping_basket),
color: Theme.of(context).colorScheme.secondary,
onPressed: () {
cart.addProduct(product.id!, product.title, product.price);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text(
"Item Added To Cart",
),
duration: const Duration(seconds: 4),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
cart.removeSingleItem(product.id!);
},
),
),
);
},
),
),
child: GestureDetector(
onTap: () {
Navigator.of(context).pushNamed(ProductDescriptionScreen.routName,
arguments: product.id);
},
child: Image.network(
product.imageUrl,
fit: BoxFit.cover,
),
),
));
}
}
Here is my Product_overview_scree where is use the appDrawer
enum FillterdOption {
favorites,
show,
}
class ProductOverviewScreen extends StatefulWidget {
const ProductOverviewScreen({Key? key}) : super(key: key);
@override
State<ProductOverviewScreen> createState() => _ProductOverviewScreenState();
}
class _ProductOverviewScreenState extends State<ProductOverviewScreen> {
var _showOnlyFav = false;
var _isInIt = true;
var _isLoaded = false;
@override
void didChangeDependencies() {
if (_isInIt) {
setState(() {
_isLoaded = true;
});
Provider.of<ProductsProvider>(context).fetchAndSetProduct().then((_) {
setState(() {
_isLoaded = false;
});
});
}
_isInIt = false;
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Product Overview"),
actions: <Widget>[
PopupMenuButton(
onSelected: (FillterdOption value) {
setState(() {
if (value == FillterdOption.favorites) {
_showOnlyFav = true;
} else if (value == FillterdOption.show) {
_showOnlyFav = false;
}
});
},
itemBuilder: (_) => [
const PopupMenuItem(
value: FillterdOption.favorites,
child: Text(
"Show Favroites",
),
),
const PopupMenuItem(
value: FillterdOption.show,
child: Text(
"Show All",
),
)
],
icon: const Icon(Icons.more_vert),
),
Consumer<Cart>(
builder: (_, cart, ch) =>
Badge(value: cart.getCountLenght.toString(), child: ch!),
child: IconButton(
icon: const Icon(Icons.shopping_cart),
onPressed: () {
Navigator.of(context).pushNamed(CartScreen.routName);
},
),
)
],
),
drawer: const AppDrawer(),
body: _isLoaded
? const Center(
child: CircularProgressIndicator(),
)
: ProductsGrid(showOnlyfac: _showOnlyFav),
);
}
}
Here is the error Stack-trace
════════ Exception caught by widgets library ═══════════════════════════════════
The following _CastError was thrown building _InheritedProviderScope<ProductsProvider?>(dirty, dependencies: [_InheritedProviderScope<Auth?>], value: Instance of 'ProductsProvider', listening to value):
type 'Null' is not a subtype of type 'String' in type cast
The relevant error-causing widget was
ChangeNotifierProxyProvider<Auth, ProductsProvider>
package:shop_app/main.dart:31
When the exception was thrown, this was the stack
#0 Auth.token
package:shop_app/provider/auth.dart:28
#1 MyApp.build.<anonymous closure>
package:shop_app/main.dart:34
#2 new ListenableProxyProvider.<anonymous closure>
package:provider/src/listenable_provider.dart:122
#3 _CreateInheritedProviderState.build
package:provider/src/inherited_provider.dart:852
#4 _InheritedProviderScopeElement.build
package:provider/src/inherited_provider.dart:546
#5 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4878
#6 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#7 BuildOwner.buildScope
package:flutter/…/widgets/framework.dart:2667
#8 WidgetsBinding.drawFrame
package:flutter/…/widgets/binding.dart:882
#9 RendererBinding._handlePersistentFrameCallback
package:flutter/…/rendering/binding.dart:378
#10 SchedulerBinding._invokeFrameCallback
package:flutter/…/scheduler/binding.dart:1175
#11 SchedulerBinding.handleDrawFrame
package:flutter/…/scheduler/binding.dart:1104
#12 SchedulerBinding._handleDrawFrame
package:flutter/…/scheduler/binding.dart:1015
#13 _invoke (dart:ui/hooks.dart:148:13)
#14 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:318:5)
#15 _drawFrame (dart:ui/hooks.dart:115:31)
════════════════════════════════════════════════════════════════════════════════
Application finished.
Exited
CodePudding user response:
The problem is the folowing line:
return null as String;
null
cannot be converted to String
with the cast operator as
. I'd recommend changing it to return an empty String
instead. Something like the below code snippet:
String get token {
if (_expiryDate != null &&
_expiryDate!.isAfter(DateTime.now()) &&
_token != null) {
return _token!;
}
return '';
}
I'd also change the ChangeNotifierProxyProvider<Auth, ProductsProvider>
to check if there's some auth in place before constructing a new ProductsProvider
(The same for ChangeNotifierProxyProvider<Auth, Order>
for the Order
). Something like the below code snippet:
ChangeNotifierProxyProvider<Auth, ProductsProvider>(
create: (ctxx) => ProductsProvider('', '', []),
update: (ctx, auth, previousProduct) => auth.isAuth
? ProductsProvider(
auth.token,
auth.userId,
previousProduct == null ? [] : previousProduct.items,
)
: previousProduct,
),
ChangeNotifierProxyProvider<Auth, Order>(
create: (ctxx) => Order('', '', []),
update: (ctx, auth, previousOrder) => auth.isAuth
? Order(auth.token, auth.userId,
previousOrder == null ? [] : previousOrder.orders)
: previousOrder,
),