异步调用值首次为NULL,在构建MainPage时会导致断言错误(窗口小部件脏)

时间:2018-12-22 18:17:56

标签: dart flutter

我正在开发一个flutter小部件,该小部件需要使用rest调用来加载和更新Text中的数据。异步调用fetchPatientCount从REST资源中获取数据,并在counter方法内更新setState

以下实现的结果是,由于build方法被调用了两次,因此counter的值第一次为NULL并导致以下异常。填充。

 flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
    flutter: The following assertion was thrown building MainPage(dirty, state: _MainPageState#9e9d8):
    flutter: 'package:flutter/src/widgets/text.dart': Failed assertion: line 235 pos 15: 'data != null': is not
    flutter: true. 

与该问题有关的任何帮助将不胜感激。enter image description here

class MainPage extends StatefulWidget {
  @override
  _MainPageState createState() => _MainPageState();
}  

class _MainPageState extends State<MainPage> {
  String counter;

  @override
  void initState() {
    super.initState();
    fetchPatientCount().then((val) {
      setState(() {
        counter = val.count.toString();
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    String text;
    if(counter!=null) {
      text = counter;
    }

    return Scaffold(
        appBar: AppBar(
          elevation: 2.0,
          backgroundColor: Colors.white,
          title: Text('Dashboard',
              style: TextStyle(
                  color: Colors.black,
                  fontWeight: FontWeight.w700,
                  fontSize: 30.0)),
        ),
        body: StaggeredGridView.count(
          crossAxisCount: 2,
          crossAxisSpacing: 12.0,
          mainAxisSpacing: 12.0,
          padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
          children: <Widget>[
            _buildTile(
              Padding(
                padding: const EdgeInsets.all(24.0),
                child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: <Widget>[
                      Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          Text('Total Views',
                              style: TextStyle(color: Colors.blueAccent)),
                          Text(text,/* Here text is NULL for the first time */
                              style: TextStyle(
                                  color: Colors.black,
                                  fontWeight: FontWeight.w700,
                                  fontSize: 34.0))
                        ],
                      ),
                      Material(
                          color: Colors.blue,
                          borderRadius: BorderRadius.circular(24.0),
                          child: Center(
                              child: Padding(
                            padding: const EdgeInsets.all(16.0),
                            child: Icon(Icons.timeline,
                                color: Colors.white, size: 30.0),
                          )))
                    ]),
              ),
            ),
          ],
          staggeredTiles: [StaggeredTile.extent(2, 110.0)],
        ));
  }


  Widget _buildTile(Widget child, {Function() onTap}) {
    return Material(
        elevation: 14.0,
        borderRadius: BorderRadius.circular(12.0),
        shadowColor: Color(0x802196F3),
        child: InkWell(
            // Do onTap() if it isn't null, otherwise do print()
            onTap: onTap != null
                ? () => onTap()
                : () {
                    print('Not set yet');
                  },
            child: child));
  }
}

class PatientCount {
  int count;
  double amount;

  PatientCount({this.count, this.amount});
  PatientCount.fromJson(Map<String, dynamic> map)
      : count = map['count'],
        amount = map['amount'];
}

Future<PatientCount> fetchPatientCount() async {
  var url = "http://localhost:9092/hms/patients-count-on-day";

  Map<String, String> requestHeaders = new Map<String, String>();
  requestHeaders["Accept"] = "application/json";
  requestHeaders["Content-type"] = "application/json";

  String requestBody = '{"consultedOn":' + '16112018' + '}';

  http.Response response =
      await http.post(url, headers: requestHeaders, body: requestBody);

  final statusCode = response.statusCode;
  final Map responseBody = json.decode(response.body);

  if (statusCode != 200 || responseBody == null) {
    throw new Exception(
        "Error occured : [Status Code : $statusCode]");
  }
  return PatientCount.fromJson(responseBody['responseData']['PatientCountDTO']);
}

3 个答案:

答案 0 :(得分:1)

如果为空,则构建一个正在加载的小部件。它将在您提到的第二个调用中构建实际的小部件。

基本上,请执行以下操作:

 @override
  Widget build(BuildContext context) {
    String text;
    if(counter!=null) {
      text = counter;
    } else {
      return Text("loading..."); // or a fancier progress thing
    }

答案 1 :(得分:0)

fetchPatientCount().then((val) {
  setState(() {
    counter = val.count.toString();
  });
});

这是预期的行为。 “异步”表示结果最终将在以后可用,并且 then 传递给 then 的代码将被执行。

Flutter不会等待。每帧都会调用build()

也许您想更改

if(counter!=null) {
  text = counter;
}

if(counter!=null) {
  text = counter;
} else {
  text = 'waiting ...';
}

因为否则text将为空,而Text(null)会导致您收到错误消息。

答案 2 :(得分:0)

我解决了自己的问题,使用FutureBuilder解决了该问题。 这是下面的完整代码。

class PatientCount {
  int count;
  double amount;

  PatientCount({this.count, this.amount});

  PatientCount.fromJson(Map<String, dynamic> map)
      : count = map['count'],
        amount = map['amount'];
}

Future<PatientCount> fetchPatientCount() async {
  var url = "http://localhost:9092/hms/patients-count-on-day";

  Map<String, String> requestHeaders = new Map<String, String>();
  requestHeaders["Accept"] = "application/json";
  requestHeaders["Content-type"] = "application/json";

  String requestBody = '{"consultedOn":' + '16112018' + '}';

  http.Response response =
      await http.post(url, headers: requestHeaders, body: requestBody);

  final statusCode = response.statusCode;
  final Map responseBody = json.decode(response.body);

  if (statusCode != 200 || responseBody == null) {
    throw new FetchPatientCountException(
        "Error occured : [Status Code : $statusCode]");
  }
  return PatientCount.fromJson(responseBody['responseData']['PatientCountDTO']);
}


class MainPage extends StatefulWidget {
  @override
  _MainPageState createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  @override
  void initState() {
    super.initState();        
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          elevation: 2.0,
          backgroundColor: Colors.white,
          title: Text('Dashboard',
              style: TextStyle(
                  color: Colors.black,
                  fontWeight: FontWeight.w700,
                  fontSize: 30.0)),
        ),
        body: StaggeredGridView.count(
          crossAxisCount: 2,
          crossAxisSpacing: 12.0,
          mainAxisSpacing: 12.0,
          padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
          children: <Widget>[
            _buildTile(
              Padding(
                padding: const EdgeInsets.all(24.0),
                child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: <Widget>[
                      Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          Text('Total Views',
                              style: TextStyle(color: Colors.blueAccent)),
                          /*Text(get,
                              style: TextStyle(
                                  color: Colors.black,
                                  fontWeight: FontWeight.w700,
                                  fontSize: 34.0))*/
                          buildCountWidget()
                        ],
                      ),
                      Material(
                          color: Colors.blue,
                          borderRadius: BorderRadius.circular(24.0),
                          child: Center(
                              child: Padding(
                            padding: const EdgeInsets.all(16.0),
                            child: Icon(Icons.timeline,
                                color: Colors.white, size: 30.0),
                          )))
                    ]),
              ),
            ),
          ],
          staggeredTiles: [StaggeredTile.extent(2, 110.0)],
        ));
  }

  Widget _buildTile(Widget child, {Function() onTap}) {
    return Material(
        elevation: 14.0,
        borderRadius: BorderRadius.circular(12.0),
        shadowColor: Color(0x802196F3),
        child: InkWell(
            // Do onTap() if it isn't null, otherwise do print()
            onTap: onTap != null
                ? () => onTap()
                : () {
                    print('Not set yet');
                  },
            child: child));
  }

  Widget buildCountWidget() {
    Widget vistitCount = new Center(
      child: new FutureBuilder<PatientCount>(
        future: fetchPatientCount(),
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            return new Text(snapshot.data.count.toString(),
                style: TextStyle(
                    color: Colors.black,
                    fontWeight: FontWeight.w700,
                    fontSize: 34.0));
          } else if (snapshot.hasError) {
            return new Text("${snapshot.error}");
          }

          // By default, show a loading spinner
          return new CircularProgressIndicator();
        },
      ),
    );
    return vistitCount;
  }
}