Search code examples
fluttergoogle-cloud-firestorestateflutter-providerflutter-navigation

Flutter - Can't refresh data after Navigator.push with firestore


I'm trying to make a favorites widget that will display items that have been added as "favorites" for the current user. So lets say I click on a favorite item called "Pizza", it will navigate me to another screen (foodDetailsScreen.dart) and lets say I remove the item from my favorites by clicking the "favorite" icon, it will successfully do it (I can see it getting removed in Firestore), but when I press back it still shows that same item card there. It will stay there until I switch tabs and come back to this tab so the initState() of FavoritesWidget is called and that food item isn't there anymore.

I tried doing .pushReplacement from FavoritesWidget to FoodDetailsScreen and I made a button (you can still see it in the code I shared) to do another .pushReplacement back to the previous screen but I don't know where to push to.

I have bottom navigation bar which has 3 tabs, 1) homeScreen, 2) cartScreen, 3) accountScreen. The FavoritesWidget is in homeScreen.

just in case its needed

  "sdk: '>=3.1.3 <4.0.0';
  cupertino_icons: ^1.0.2
  firebase_core: ^2.24.2
  cloud_firestore: ^4.14.0
  firebase_auth: ^4.16.0
  provider: ^6.1.1

FavoritesWidget.dart

class Favorites extends StatefulWidget {
  const Favorites({super.key});

  @override
  State<Favorites> createState() => _FavoritesState();
}

class _FavoritesState extends State<Favorites> {
  @override
  void initState() {
    super.initState();
    Provider.of<FavoriteProvider>(context, listen: false).loadFavoriteItems();
  }

  @override
  Widget build(BuildContext context) {
    return Consumer<FavoriteProvider>(
      builder: (context, favoriteProvider, child) {
        return SizedBox(
          height: 180,
          width: double.infinity,
          child: Column(
            children: <Widget>[
              title(),
              Expanded(
                child: ListView.builder(
                  scrollDirection: Axis.horizontal,
                  itemCount: favoriteProvider.favoriteItems.length,
                  itemBuilder: (context, index){
                    return GestureDetector(
                      onTap: () {
                        Navigator.push(
                          context,
                          MaterialPageRoute(
                            builder: (context) => FoodDetailsScreen(foodItem: favoriteProvider.favoriteItems[index]))
                        );
                      },
                      child: SizedBox(
                        width: 220,
                        child: Card(
                          child: Padding(
                            padding: const EdgeInsets.all(8.0),
                            child: Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              crossAxisAlignment: CrossAxisAlignment.center,
                              children: [
                                Text(favoriteProvider.favoriteItems[index].name),
                                Text('\$${favoriteProvider.favoriteItems[index].price.toStringAsFixed(2)}'),
                              ],
                            ),
                          ),
                        ),
                      ),
                    );
                  },
                ),
              ),
            ],
          ),
        );
      },
    );
  }

  Widget title() {
    return Container(
      padding: const EdgeInsets.only(left: 10, right: 10, bottom: 5),
      child: const Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: <Widget>[
          Text("Favorites", style: TextStyle(fontSize: 22)),
        ],
      ),
    );
  }
}

foodDetailsScreen.dart

class FoodDetailsScreen extends StatefulWidget {
  final FoodItem foodItem;

  const FoodDetailsScreen({super.key, required this.foodItem});

  @override
  State<FoodDetailsScreen> createState() => _FoodDetailsScreenState();
}

class _FoodDetailsScreenState extends State<FoodDetailsScreen> {
  FavoriteProvider favoriteProvider = FavoriteProvider();
  late bool isFavorited;

  @override
  void initState() {
    super.initState();

    isFavorited = favoriteProvider.checkIfFavorited(widget.foodItem);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.foodItem.name),
        actions: [
          IconButton(
            padding: const EdgeInsets.only(right: 40),
            onPressed: () async {
              setState(() {
                if (isFavorited) {
                  favoriteProvider.removeFromFavorites(widget.foodItem);
                } else {
                  favoriteProvider.addToFavorites(widget.foodItem);
                }
                isFavorited = !isFavorited;
              });
            },
            color: isFavorited ? Colors.red : Colors.black,
            icon: Icon(isFavorited ? Icons.favorite : Icons.favorite_border,size: 40),
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              widget.foodItem.name,
              style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            const SizedBox(height: 16),
            Text(
              'Price: \$${widget.foodItem.price.toStringAsFixed(2)}',
              style: const TextStyle(fontSize: 18),
            ),
            const SizedBox(height: 40),
            ElevatedButton(
              onPressed: () {

              },
              child: const Text('Go back'),
            ),
          ],
        ),
      ),
    );
  }
}

favoriteProvider.dart

class FavoriteProvider extends ChangeNotifier {
  final FirebaseFirestore _firebaseFirestore = FirebaseFirestore.instance;
  final userId = FirebaseAuth.instance.currentUser!.uid;
  late CollectionReference _usersCollection;


  List<FoodItem> _favoriteItems = [];

  List<FoodItem> get favoriteItems => _favoriteItems;

  FavoriteProvider(){
    _usersCollection = _firebaseFirestore.collection('users');
  }

  Future<void> loadFavoriteItems() async {
    try {
      var snapshot = await _usersCollection.doc(userId).collection('favorites').get();
      _favoriteItems = snapshot.docs.map((doc) => FoodItem.fromFirestore(doc)).toList();
      notifyListeners();
    } catch (error) {
      print('Error loading favorite items: $error');
    }
  }

  bool checkIfFavorited(FoodItem foodItem) {
    String itemId = _favoriteItems.indexWhere((item) => item.id == foodItem.id).toString();

    if(int.parse(itemId) != -1) {
      return false;
    } else {
      return true;
    }
  }

  Future<void> addToFavorites(FoodItem foodItem) async {
    try {
      if (!_favoriteItems.any((item) => item.id == foodItem.id)) {
        await _usersCollection.doc(userId).collection('favorites').doc(foodItem.id).set({
          'id': foodItem.id,
          'name': foodItem.name,
          'price': foodItem.price,
        });
        _favoriteItems.add(FoodItem(id: foodItem.id, name: foodItem.name, price: foodItem.price));
        notifyListeners();
      }
    } catch (error) {
      print('Error adding to favorites: $error');
    }
  }

  Future<void> removeFromFavorites(FoodItem foodItem) async {
    try {
      await _usersCollection.doc(userId).collection('favorites').doc(foodItem.id).delete();
      _favoriteItems.removeWhere((item) => item.id == foodItem.id);
      notifyListeners();
    } catch (error) {
      print('Error removing to favorites: $error');
    }
  }
}

I tried:

WillPopScope(
  onWillPop: () async {
    Provider.of<FavoriteProvider>(context, listen: false).loadFavoriteItems();
    return true;
  }
  // ...
)

I also tried so many provider state solutions, none of which worked.


Solution

  • So I fixed the problem and was able to do it with 2 solutions.

    1. If I want to do do: .pushReplacement for the FoodDetailsScreen, then I will have to return .pushReplacement to the screen where I'm setting the bottomNavigationBar and the tabs, in my case its called "MainScreen"

    2. If I want to use .push then adding .then((_){ setState(() { favoriteProvider.loadFavoriteItems(); }); does the job. Before this I was just doing setState(() {});and it wasn't working, now it does with the 2) answer.