Search code examples
flutterproviderconsumerflutter-change-notifier

Why is my UI not updating with the after getting notified by change notifier


I am working on an API in Flutter after spending a long time I managed to get the data I am using a provider and change notifier to update my UI. According to the data that I get, but the UI is not updating

This is my API provider:

class ApiProvider extends ChangeNotifier{

  List<PlantModel>? _plantList = [];
  List<PlantModel>? get plantList => _plantList;

  final apiService = ApiServices(); 

  bool isLoading = true;

  void getPlantData()async{
    if (kDebugMode) {
      print("calling...function ");
    }
    _plantList = await apiService.getPlantDataCall();
    isLoading = false;
    if (kDebugMode) {
      print("notifying listners");
    }
    notifyListeners();
    if (kDebugMode) {
      print("listners notified");
      print("this is isloading == $isLoading");
    }
  }
  
} 

and my UI code :

import 'package:better_lucks/controller/api_controller.dart';
import 'package:better_lucks/controller/google_sign_in_controller.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:provider/provider.dart';

class Dashboard extends StatelessWidget {
  Dashboard({super.key});

  @override
  Widget build(BuildContext context) {
     Provider.of<ApiProvider>(context,listen:false).getPlantData();
    final user = FirebaseAuth.instance.currentUser!;

    return Scaffold(
      backgroundColor: Colors.green,
      appBar: AppBar(
        backgroundColor: Colors.transparent,
        leading:Container(margin: const EdgeInsets.all(12),
          child: CircleAvatar(backgroundImage: NetworkImage(user.photoURL!))
          ),
          title: Text(user.displayName.toString(),style:const TextStyle(color:Colors.white),),
        actions: [
          IconButton(color: Colors.white,
            onPressed: (){
            final provider = Provider.of<GoogleSignInProvider>(context,listen: false);
            provider.logout();
          }, icon:const FaIcon(FontAwesomeIcons.arrowRightFromBracket))
        ],
        ),
      body:Consumer<ApiProvider>(
        builder: (context,apiProvider,_){
        return  SizedBox(
        height: MediaQuery.of(context).size.height,
        width: MediaQuery.of(context).size.width,
        child:apiProvider.isLoading? GridView.builder(
          shrinkWrap: true,
          gridDelegate:const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 2,
            mainAxisSpacing:8,
            crossAxisSpacing: 8 
            ),
          itemCount: apiProvider.plantList!.length,
          itemBuilder: (BuildContext context,int index){
          return Card(
            child: Column(
              children: [
                Container(margin: const EdgeInsets.all(8),
                  child: CircleAvatar(
                    backgroundImage:NetworkImage(apiProvider.plantList![index].imageUrl),
                    radius: 50,
                  ),
                ),
                Text(apiProvider.plantList![index].commonName),
              ],
            ),
          );
        }):const Center(
        child: CircularProgressIndicator(),
        ),
      );
      },)
    );
  }
}

Here is the output from terminal:

I/flutter (16721): we got the response body :
I/flutter (16721): [Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel', Instance of 'PlantModel']
I/flutter (16721): notifying listners
I/flutter (16721): listners notified
I/flutter (16721): this is isloading == false

there was no error in the terminal I guess the UI is not updating because I did some mistake, I tried making the listen: true(located after widget build) when I call to get data but it calls API so many times instead of one.


Solution

  • How are you creating your provider and how are you interacting with your ApiProvider?

    And how are you updating your provider, or refreshing the data from the api? Only the call Provider.of(context,listen:false).getPlantData(); inside of your build method? This will only refresh the api data when your widget is already rebuilding and will not work.

    The provided ChangeNotifier from the Provider will only force the Consumer widget to rebuild when notifyListener() is called, but something still has to call that in the first place.

    Also it looks to me like your child:apiProvider.isLoading? GridView.builder( is the wrong way (displaying the grid when loading is true).

    I created a slightly modified example from your code that works like you expect it to. It shows the loading indicator for 2 seconds and then displays one entry. And afterwards you can refresh the api with a button to add more entries. But of course you could also create a Timer inside of a State object of a StatefulWidget that automatically calls refresh every second, or so:

    import 'package:flutter/foundation.dart';
    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';
    
    void main() => runApp(MaterialApp(
        home: Scaffold(
            backgroundColor: Colors.green,
            appBar: AppBar(),
            body: Builder(builder: (BuildContext context) => _build(context)))));
    
    Widget _build(BuildContext context) {
      return ChangeNotifierProvider<ApiProvider>(create: (_) => ApiProvider()..getPlantData(), child: Dashboard());
    }
    
    class PlantModel {
      int someData = 0;
    }
    
    class ApiProvider extends ChangeNotifier {
      List<PlantModel> _plantList = [];
    
      List<PlantModel> get plantList => _plantList;
    
      bool isLoading = true;
    
      void getPlantData() async {
        isLoading = true;
        notifyListeners();
        if (kDebugMode) {
          print("calling...function ");
        }
        await Future<void>.delayed(const Duration(seconds: 2));
        _plantList.add(PlantModel()..someData = _plantList.length);
        isLoading = false;
        if (kDebugMode) {
          print("notifying listners");
        }
        notifyListeners();
        if (kDebugMode) {
          print("listners notified");
          print("this is isloading == $isLoading");
        }
      }
    }
    
    class Dashboard extends StatelessWidget {
      Dashboard({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: [
            ElevatedButton(
              onPressed: () => Provider.of<ApiProvider>(context, listen: false).getPlantData(),
              child: const Text("REFRESH"),
            ),
            Expanded(child: _buildConsumer(context)),
          ],
        );
      }
    
      Widget _buildConsumer(BuildContext context) {
        return Consumer<ApiProvider>(
          builder: (BuildContext context, ApiProvider apiProvider, _) {
            return SizedBox(
              height: MediaQuery.of(context).size.height,
              width: MediaQuery.of(context).size.width,
              child: apiProvider.isLoading == false
                  ? GridView.builder(
                      shrinkWrap: true,
                      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                          crossAxisCount: 2, mainAxisSpacing: 8, crossAxisSpacing: 8),
                      itemCount: apiProvider.plantList!.length,
                      itemBuilder: (BuildContext context, int index) {
                        return Card(
                          child: Column(
                            children: [
                              Container(
                                margin: const EdgeInsets.all(8),
                              ),
                              Text("${apiProvider.plantList![index].someData}"),
                            ],
                          ),
                        );
                      },
                    )
                  : const Center(
                      child: CircularProgressIndicator(),
                    ),
            );
          },
        );
      }
    }