1

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
4
  • where did you use AppDrawer? Commented Sep 8, 2022 at 6:48
  • In my product_overview_screen file see the I attached it now Commented Sep 8, 2022 at 12:16
  • Would you please also post the error stack-trace? Commented Sep 8, 2022 at 15:37
  • See I just posted the error stack-trace Commented Sep 8, 2022 at 16:35

2 Answers 2

1

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,
),
Sign up to request clarification or add additional context in comments.

Comments

0

I faced the same Error, the Solution posted here don't work for me but this did,

in your authentication file add ? to String?

 String? get token {
    if (_expiryDate != null &&
        _expiryDate!.isAfter(DateTime.now()) &&
        _token != null) {
      return _token;
    }
    return null;
  }

and the in the main.dart use ! in auth.token!

       ChangeNotifierProxyProvider<Auth, Products>(
          create: (_) => Products("", []),
          update: (ctx, auth, previousProducts) => Products(
            auth.token!,
            previousProducts == null ? [] : previousProducts.items,
          ),
          
        ),

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.