Search code examples
flutterdartflutter-layout

setState is not updating the UI


I am trying to fetch data from the API and I am able to get logs but setState is not working. Overall what I want to achieve is if there is response show the data on the screen, if there is any error in the API or on server or anything else I want to show it in the snackbar. My moto is to show errors as well.

Below is my model class

import 'http.dart';

class User {
int userId;
int id;
String title;
String body;

User({this.userId, this.id, this.title, this.body});

User.fromJson(Map<String, dynamic> json) {
  userId = json['userId'];
  id = json['id'];
  title = json['title'];
  body = json['body'];
}

Map<String, dynamic> toJson() {
  final Map<String, dynamic> data = new Map<String, dynamic>();
  data['userId'] = this.userId;
  data['id'] = this.id;
  data['title'] = this.title;
  data['body'] = this.body;
  return data;
}


}


class UserExt {

static getUserInfo(Function(User user) success, Function(String errorMesssage) error) async{

  final response = await HTTP.get(api: "https://jsonplaceholder.typicode.com/posts/1");
  if(response.isSuccess == true) {
    success(User.fromJson(response.response));
  } else {
    error(response.response);
  }

}

}

Below is my http.dart file

import 'dart:html';
import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;
import 'dart:convert' as convert;
import 'package:http/http.dart';

const _timeoutDuration = Duration(seconds: 5);

class HTTP {


  static Future<HttpResponse> get({@required String api}) async {

    try {
      Response response = await http.get(api).timeout(_timeoutDuration);
      return _modeledResponse(response);
    } catch (error) {
      return HttpResponse(isSuccess: false, response: error.toString());
    }


  }

  static Future<HttpResponse> _modeledResponse(Response response) async {

    try {
      if(response.statusCode == HttpStatus.ok) {
        var jsonResponse = convert.jsonDecode(response.body);
        return HttpResponse(isSuccess: true, response: jsonResponse);
      } else {
        return HttpResponse(isSuccess: false, response: response.statusCode.toString());
      }
    } catch (error) {
      return HttpResponse(isSuccess: false, response: error.toString());
    }

  }



}




class HttpResponse {
  final bool isSuccess;
  final dynamic response;
  HttpResponse({@required this.isSuccess, @required this.response});
}

Below is my screen from where I am calling the API.

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http_request/User.dart';
import 'http.dart';






class ApiCalling extends StatefulWidget {
  @override
  _ApiCallingState createState() => _ApiCallingState();
}

class _ApiCallingState extends State<ApiCalling> {

  bool showLoader = false;

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      body: Center(
        child: Stack(
          children: <Widget>[


            Center(
              child: RaisedButton(
                child: Text("Call API"),
                onPressed: () {

                  setState(() {
                    showLoader = true;
                  });
                  UserExt.getUserInfo((user){
                    print("UUUser id = ${user.userId}");
                    Scaffold.of(context).showSnackBar(SnackBar(content:     Text("${user.userId}"),));

                setState(() {
                  showLoader = false;
                });


              }, (error){
                Scaffold.of(context).showSnackBar(SnackBar(content: Text("${error}"),));
                setState(() {
                  showLoader = false;
                });
              });


            },
          ),
        ),
        Visibility(child: CircularProgressIndicator(backgroundColor: Colors.pink,), visible: showLoader,),


      ],
    ),
  ),
);
  }
}

In the current code indicator is not getting show/hide or snackbar is also not getting displayed.


Solution

  • From the official Documentation of flutter https://api.flutter.dev/flutter/material/Scaffold/of.html

    When the Scaffold is actually created in the same build function, the context argument to the build function can't be used to find the Scaffold (since it's "above" the widget being returned in the widget tree). In such cases, the following technique with a Builder can be used to provide a new scope with a BuildContext that is "under" the Scaffold:

    So basically the problem is with your context of Scaffold,so instead of using context of Direct parent that instantiate the Scaffold, use the context of the child.

    Below code will work.

    class _ApiCallingState extends State<ApiCalling> {
      bool showLoader = false;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Builder(
            builder: (context)=>
                Center(
            child: Stack(
              children: <Widget>[
                Center(
                  child: RaisedButton(
                    child: Text("Call API"),
                    onPressed: () {
                      setState(() {
                        showLoader = true;
                      });
                      UserExt.getUserInfo((user) {
                        print("UUUser id = ${user.userId}");
                        print("context==$context");
                        Scaffold.of(context).showSnackBar(SnackBar(
                          content: Text(" User Id${user.userId}"),
                        ));
                         setState(() {
                          showLoader = false;
    
                        });
    
    
    
                      }, (error) {
                        setState(() {
                          showLoader = false;
                        });
                        Scaffold.of(context).showSnackBar(SnackBar(
                          content: Text("${error}"),
                        ));
    
                      });
                    },
                  ),
                ),
                Visibility(
                  child:
                  Center(
                    child:CircularProgressIndicator(
                      backgroundColor: Colors.pink,
                    ),
    
                  ),
                  visible: showLoader,
                )
              ],
            ),
          ),
          )
    
        );
      }
    }