KD11
KD11

Reputation: 21

Flutter: ChangeNotifierProxyProvider: not providing updated correct state

I am trying to get state from one ChangeNotifier Auth.dart into another ChangeNotifier ProductsProvider.dart. But ChangeNotifierProxyProvider is providing incorrect state data for Auth.

main.dart

void main() => runApp(
      MultiProvider(
        providers: [
          ChangeNotifierProvider<Auth>(create: (ctx) => Auth()),
          ChangeNotifierProxyProvider<Auth, ProductsProvider>(
            create: (context) => ProductsProvider(
              Provider.of<Auth>(context, listen: false),
              productList: [],
            ),
            update: (ctx, auth, preProducts) {
              print("ChangeNotifierProxyProvider Token ${auth.isAuth}");
              print("ChangeNotifierProxyProvider Token ${auth.token}");
              print("ChangeNotifierProxyProvider Test ${auth.test}");
              return ProductsProvider(
                auth,
                productList:
                    preProducts == null ? [] : preProducts.getProductList,
              );
            },
          ),
          ChangeNotifierProvider(create: (ctx) => Cart()),
          ChangeNotifierProvider(create: (ctx) => Order()),
          ChangeNotifierProvider(create: (ctx) => Auth()),
        ],
        child: MyApp(),
      ),
    );

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<Auth>(
      builder: (ctx, auth, _) {
        print("Builder Token ${auth.isAuth}");
        print("Builder Token ${auth.token}");
        print("Builder Test ${auth.test}");
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.purple,
            accentColor: Colors.orange,
            fontFamily: "Lato",
          ),
          routes: {
            "/": (ctx) => auth.isAuth ? ProductOverviewScreen() : AuthScreen(),
            ProductDetailScreen.PRODUCT_DETAIL_ROUTE: (ctx) =>
                ProductDetailScreen(),
            CartScreen.CART_SCREEN_ROUTE: (ctx) => CartScreen(),
            OrderScreen.ORDER_SCREEN_ROUTE: (ctx) => OrderScreen(),
            UserProductScreen.USER_PRODUCT_ROUTE: (ctx) => UserProductScreen(),
            EditProductScreen.EDIT_PRODUCT_ROUTE: (ctx) => EditProductScreen(),
          },
        );
      },
      // ),
    );
  }
}

Auth.dart

import 'dart:convert';

import 'package:flutter/widgets.dart';
import 'package:http/http.dart' as http;
import 'package:shop_app/models/HttpException.dart';

class Auth with ChangeNotifier {
  String _token;
  String _userId;
  DateTime _expiryDate;
  String test;

  bool get isAuth => token != null;

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

  Future<void> _authenticate(String email, String password, String url) async {
    final uri = Uri.parse(url);

    final response = await http.post(
      uri,
      body: jsonEncode({
        "email": email,
        "password": password,
        "returnSecureToken": true,
      }),
    );

    Map<String, dynamic> responseData = jsonDecode(response.body);
    if (responseData["error"] != null) {
      throw HttpException(responseData["error"]["message"]);
    }

    _token = responseData["idToken"];
    _userId = responseData["localId"];
    test = "_token";
    // _expiryDate = DateTime.now()
    //     .add(Duration(seconds: int.parse(responseData["expiresIn"])));
    notifyListeners();
  }

  Future<void> signUp(String email, String password) async {
    const url =
        "https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=[KEY]";
    return await _authenticate(email, password, url);
  }

  Future<void> login(String email, String password) async {
    const url =
        "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=[KEY]";
    return await _authenticate(email, password, url);
  }
}

ProductsProvider.dart

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shop_app/models/HttpException.dart';
import 'package:shop_app/models/Product.dart';
import 'package:shop_app/providers/Auth.dart';

class ProductsProvider with ChangeNotifier {
  List<Product> _productList = [];

  Auth _auth;

  ProductsProvider(this._auth, {List<Product> productList = const []})
      : _productList = productList;

  Future<void> fetchAndSetProducts() async {
    var uri = Uri.parse(
        "https://flutter-shop-app-3035a-default-rtdb.europe-west1.firebasedatabase.app/products.json?auth=${_auth.token}");
    try {
      final response = await http.get(uri);
      if (response.body != "null") {
        final Map<String, dynamic> decodedJSON = jsonDecode(response.body);
        final List<Product> loadedProductList = [];

        decodedJSON.forEach((prodId, prodData) {
          loadedProductList.add(Product(
            id: prodId,
            title: prodData["title"],
            description: prodData["description"],
            price: prodData["price"],
            imageUrl: prodData["imageUrl"],
            isFavourite: prodData["isFavourite"],
          ));
        });
        _productList = loadedProductList;
      } else {
        _productList = [];
      }
      notifyListeners();
    } catch (error) {
      throw error;
    }
  }

  List<Product> get getProductList {
    return _productList;
  }

  Product findById(String id) =>
      _productList.firstWhere((element) => element.id == id);

  Future<void> addProduct(Product product) async {
    var uri = Uri.parse(
        "https://flutter-shop-app-3035a-default-rtdb.europe-west1.firebasedatabase.app/products.json");

    try {
      final responseProduct =
          await http.post(uri, body: jsonEncode(product.toJSon()));

      final finalProduct = Product(
          id: jsonDecode(responseProduct.body)["name"],
          title: product.title,
          description: product.description,
          price: product.price,
          imageUrl: product.imageUrl);

      _productList.add(finalProduct);
      notifyListeners();
      return Future.value();
    } catch (error) {
      throw error;
    }
  }

  Future<void> updateProduct(String id, Product product) async {
    var productIndex = _productList.indexWhere((element) => element.id == id);
    if (productIndex >= 0) {
      var uri = Uri.parse(
          "https://flutter-shop-app-3035a-default-rtdb.europe-west1.firebasedatabase.app/products/$id.json");

      try {
        await http.patch(uri,
            body: jsonEncode({
              "title": product.title,
              "description": product.description,
              "price": product.price,
              "imageUrl": product.imageUrl,
              "isFavourite": product.isFavourite,
            }));

        _productList[productIndex] = product;
        notifyListeners();
      } catch (error) {
        throw error;
      }
    }
  }

  Future<void> deleteProduct(String id) async {
    final uri = Uri.parse(
        "https://flutter-shop-app-3035a-default-rtdb.europe-west1.firebasedatabase.app/products/$id.json");

    final existingProductIndex =
        _productList.indexWhere((element) => element.id == id);
    var existingProduct = _productList[existingProductIndex];
    _productList.removeAt(existingProductIndex);
    final response = await http.delete(uri);
    if (response.statusCode >= 400) {
      _productList.insert(existingProductIndex, existingProduct);
      notifyListeners();

      throw HttpException("Could not delete product");
    }
    existingProduct = null;
    notifyListeners();
  }
}

After I click the login button the login method from the Auth.dart is trigged. After fetching the token the from firebase the screen is updated to ProductOverviewScreen. But the ProductsProvider.dart is not able to fetch the items because the ChangeNotifierProxyProvider update is returning an incorrect state for Auth.

Output:

Builder Token true
Builder Token eyJhbGciOiJSUzI1NiIsImtpZCI6IjNkOWNmYWE4OGVmMDViNDI0YmU2MjA1ZjQ2YjE4OGQ3MzI1N2JjNDIiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vZmx1dHRlci1zaG9wLWFwcC0zMDM1YSIsImF1ZCI6ImZsdXR0ZXItc2hvcC1hcHAtMzAzNWEiLCJhdXRoX3RpbWUiOjE2MjE3NzUxMDYsInVzZXJfaWQiOiJ5Y0dVVWhwYTFvY2EwMThlYUx4VGZkQnRNbmsyIiwic3ViIjoieWNHVVVocGExb2NhMDE4ZWFMeFRmZEJ0TW5rMiIsImlhdCI6MTYyMTc3NTEwNiwiZXhwIjoxNjIxNzc4NzA2LCJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsidGVzdEB0ZXN0LmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.t1xosbzllt79NV6FQ79mTQ2J3VCR5fILKMxE5-ObOxMI2DtD_kMg2AP9NXm_f1IsLF9AT5xeXeVU36goVDLQuKSWmbejOANDn7hsF6VzZMyBV1P9qehXpWgSmCscjXT8FRlKViZzxOZwCWSHS1M94n92YYhwaZltiDcQ87hhv7ZdKyLrlDsUPfr7IjNBeSVDmzws_9uBoZwYKRYCW1veWPc7HPWtdP8QT7K_vkCEvGLHfxbmVHOgUkDMzLqhgusZl34GCPVKr_PSpQ9SgC7Mg95QeZyzYzPmhasGUptq5pQsjEoqTxgYHnmEuMRRjksZT5lbfsQQFOJMsXBTIC7RDQ
Builder Test _token
ChangeNotifierProxyProvider Token false
ChangeNotifierProxyProvider Token null
ChangeNotifierProxyProvider Test null
Error: Expected a value of type 'int', but got one of type 'String'
    at Object.throw_ [as throw] (http://localhost:1344/dart_sdk.js:5333:11)
    at ProductProvider.ProductsProvider.new.fetchAndSetProducts (http://localhost:1344/packages/shop_app/providers/ProductProvider.dart.lib.js:84:21)
    at fetchAndSetProducts.next (<anonymous>)
    at http://localhost:1344/dart_sdk.js:39031:33
    at _RootZone.runUnary (http://localhost:1344/dart_sdk.js:38888:58)
    at _FutureListener.thenAwait.handleValue (http://localhost:1344/dart_sdk.js:33874:29)
    at handleValueCallback (http://localhost:1344/dart_sdk.js:34434:49)
    at Function._propagateToListeners (http://localhost:1344/dart_sdk.js:34472:17)
    at _Future.new.[_completeWithValue] (http://localhost:1344/dart_sdk.js:34314:23)
    at async._AsyncCallbackEntry.new.callback (http://localhost:1344/dart_sdk.js:34337:35)
    at Object._microtaskLoop (http://localhost:1344/dart_sdk.js:39175:13)
    at _startMicrotaskLoop (http://localhost:1344/dart_sdk.js:39181:13)
    at http://localhost:1344/dart_sdk.js:34688:9

I am learning dart and flutter. I am not sure what I am missing. Can someone could help me fix this issue.

Upvotes: 1

Views: 1228

Answers (2)

Thang PhamVan
Thang PhamVan

Reputation: 39

Product Id defined int/Integer, but the parser from firebase is String. You need change in Product Id to String or use int.parse(prodId)

Upvotes: 0

KD11
KD11

Reputation: 21

I was able to find what was causing this issue. It appears I was reinitializing the Auth ChangeNotifierProvider. If you see the last line in the MultiProvider constructor providers argument.

If anyone else comes across such an issue ensure that you have specified the providers in the correct order.

Upvotes: 1

Related Questions