Flutter Provider "notifyListener()" does not change the state

484

You need to set listen to true when you are expecting updates:

 bool isLoading =
        Provider.of<LoginProvider>(context, listen: true).isLoading;
    User user = Provider.of<LoginProvider>(context, listen: true).user;
Share:
484
dev1ce
Author by

dev1ce

Updated on January 01, 2023

Comments

  • dev1ce
    dev1ce over 1 year

    I have a method login in my Provider class which is called in initState() method of my HomeScreen(), in login function I basically register/check if a user information is stored in server or not, and then I post request to server to get that user's info. Everything is working fine but when I hot restart or reopens the app, it keeps on loading because obviously the ternary operation doesn't satisfy but If I print the data inside the login function, it does get changed, so as a result, the notifyListeners() is not working properly and hence the state is not getting changed. (Right now I have to hot reload ctrl+s, and then the state changes and everything loads successfully).

    HomeScreen():

    class HomeScreen extends StatefulWidget {
      const HomeScreen({Key? key}) : super(key: key);
    
      @override
      _HomeScreenState createState() => _HomeScreenState();
    }
    
    class _HomeScreenState extends State<HomeScreen> {
      List<Course> courses = [];
    
      @override
      void initState() {
        super.initState();
        courses = Course().getAllCourses();
        Future.delayed(Duration.zero, () async {
          Provider.of<LoginProvider>(context, listen: false).login();
        });
      }
    
      @override
      Widget build(BuildContext context) {
        bool isLoading =
            Provider.of<LoginProvider>(context, listen: false).isLoading;
        User user = Provider.of<LoginProvider>(context, listen: false).user;
        print("INITIAL: $isLoading");
        print("INITIAL: ${user.data!.categoryList!.length}");
    
        return Scaffold(
          backgroundColor: Colors.white,
          body: !isLoading && user.data != null
              ? ListView.separated(
                  itemCount: courses.length,
                  separatorBuilder: (context, index) {
                    return Divider();
                  },
                  itemBuilder: (context, index) {
                    final course = courses[index];
                    final courseNew = user.data!.categoryList![index];
                    print("INSIDE: $isLoading");
                    print("INSIDE: ${user.data!.categoryList!.length}");
                    if (courses.length == user.data!.categoryList!.length) {
                      return ... // full custom
                    }
                    return Center(
                      child: Text("No Data recieved from backend"),
                    );
                  },
                )
              : Center(
                  child: CircularProgressIndicator(
                  strokeWidth: 4,
                )),
        );
      }
    }
    

    (Both courses and user.data.categoryList have same length)

    Login function in Provider file:

    login() async {
        await _checkIsUserLoggedIn();
        if (_deviceDetails.deviceID == "") {
          print("[INFO] NEW USER DEVICE DETECTED");
          print("[INFO] LOGGING IN...");
    
          await _getDeviceDetails();
          // fetching platform details, if success, carry-on, else throw error
    
          try {
            if (_deviceDetails.deviceID != "") {
              // post request to server
              Map<String, dynamic> requestBody = {
                'device_id': _deviceDetails.deviceID
              };
    
              http.Response response = await http
                  .post(Uri.parse(Constants.LOGIN_ENDPOINT), body: requestBody);
    
              _user = userFromJson(response.body);
              _deviceDetails.userID = _user.data!.userId!;
              _deviceDetails.userName = _user.data!.name!;
              _deviceDetails.token = _user.data!.token!;
    
              await _saveLoginDetails(_deviceDetails);
              print("[INFO] USER LOGGED IN SUCCESSFULLY");
            }
          } catch (error) {
            print("[INFO] ERROR WHILE FETCHING DEVICE DETAILS: $error");
          }
        } else {
          print("[INFO] THIS USER DEVICE IS ALREADY LOGGED IN");
          print("[INFO] FETCHING EXISTING USER DETAILS");
    
          Map<String, dynamic> requestBody = {'device_id': _deviceDetails.deviceID};
    
          http.Response response = await http
              .post(Uri.parse(Constants.LOGIN_ENDPOINT), body: requestBody);
          _user = userFromJson(response.body);
          print("INSIDE LOGIN FUNCTION: ${_user.data!.categoryList!.length}"); // prints 2 in console (correct)
        }
        _isLoading = false;
        notifyListeners();
      }
    

    Console output:

    Output when app first loads or Hot Restart:

    I/flutter ( 3495): INITIAL: true
    I/flutter ( 3495): INITIAL: 1
    I/flutter ( 3495): [INFO] THIS USER DEVICE IS ALREADY LOGGED IN
    I/flutter ( 3495): [INFO] FETCHING EXISTING USER DETAILS
    I/flutter ( 3495): INSIDE LOGIN FUNCTION: 2
    

    Ouput when app Hot Reloads (Ctrl+S):

    I/flutter ( 3495): INITIAL: false
    I/flutter ( 3495): INITIAL: 2
    I/flutter ( 3495): INSIDE: false
    I/flutter ( 3495): INSIDE: 2
    I/flutter ( 3495): INSIDE: false
    I/flutter ( 3495): INSIDE: 2
    

    As you can see, after hot reloading, the state changes but not without it, so that means notifyListeners() is not behaving properly in login function?

    What could possible be wrong here, I'm really confused...

    Any help would be really appreciated!

  • dev1ce
    dev1ce over 2 years
    Hi, thank you for your response, Can you explain your answer a bit? and what was wrong with my code?
  • Hasib Akon
    Hasib Akon over 2 years
    Basically, notifyListeners() work like setState({}) means it will call the build function again that's why it is calling again and again. Now we case use consumer to wrap the section will be updated then the variable will update .
  • dev1ce
    dev1ce over 2 years
    It worked! If we do not provide listen argument, it is not set to true by default? and I have read somewhere that if we do not provider listen argument, notifyListeners() will keep on updating the widget...
  • Huthaifa Muayyad
    Huthaifa Muayyad over 2 years
    It will not work as desired without setting listen:true. You basically need to tell it, because logically, you already know what you want your function to do. Something to keep in mind, is if you want to use your provider inside an onPressed. There, you only have the option to set listen:false, otherwise it will throw an error. But this is not the case in your post. Happy coding! :)