首页 > 解决方案 > 字符串列表不会在 setState 上更新

问题描述

我正在尝试学习 Flutter,但无法理解这里出了什么问题。我有主小部件(Flutter starter 应用程序附带的那个),我删除了主体,并添加了一个SearchBar小部件和一个PhotoList小部件,我正在尝试从 API 获取数据,并更新 PhotoList 小部件以显示图像我取了。

这就是我所拥有的:

class _MyHomePageState extends State<MyHomePage> {
  int _selectedSegment = 0;
  Networking _service = Networking();
  List<String> urls;

  @override
  void initState() {
    urls = [];
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        margin: EdgeInsets.only(top: 100),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                SearchBar(
                  onSearchTap: (String value) {
                    Map<String, dynamic> map;
                    List<dynamic> results;
                    dynamic urls;
                    dynamic thumbs;
                    List<String> image_urls = new List<String>();

                    _service.fetchPhotos(value).then((value) => {
                          map = jsonDecode(value.body),
                          results = map["results"],
                          thumbs = results.map((e) => e["urls"]),
                          for (dynamic thumb in thumbs)
                            {image_urls.add(thumb["thumb"].toString())},
                          this.setState(() {
                            urls = image_urls;
                          })
                        });
                  },
                ),
              ],
            ),
            Padding(
              padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  ButtonGroup(
                    titles: ["Photos", "Collections", "User"],
                    current: _selectedSegment,
                    onTab: (selected) {
                      setState(() {
                        _selectedSegment = selected;
                      });
                    },
                  ),
                ],
              ),
            ),
            PhotosGrid(urls: urls),
          ],
        ),
      ),
    );
  }
}

我在 setState 之后打印了 url,它确实有打印到控制台的值。但它不会在 PhotosGrid 中更新,据我了解,setState 应该刷新build函数,所以 PhotosGrid 也应该刷新,但没有任何反应。这是我的 PhotosGrid 小部件:

class PhotosGrid extends StatefulWidget {
  List<String> urls;

  PhotosGrid({Key key, this.urls}) : super(key: key);
  @override
  _PhotosGridState createState() => _PhotosGridState();
}

class _PhotosGridState extends State<PhotosGrid> {
  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: Container(
        child: ListView(
          children: <Widget>[
            for (var url in widget.urls) PhotoCard(url)
            //for (var url in widget.urls) PhotoCard(url),
          ],
        ),
      ),
    );
  }
}

这是我的照片卡:

class PhotoCard extends StatefulWidget {
  String imageUrl;

  PhotoCard({Key key, this.imageUrl}) : super(key: key);

  @override
  _PhotoCardState createState() => _PhotoCardState();
}

class _PhotoCardState extends State<PhotoCard> {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Padding(
        padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 32),
        child: Container(
          height: 220,
          decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(5.0),
              image: DecorationImage(
                image: NetworkImage(widget.imageUrl),
                fit: BoxFit.cover,
              )),
        ),
      ),
    );
  }
}

我不明白这里出了什么问题。

标签: flutterdart

解决方案


老实说,我觉得很奇怪,这首先不起作用

onSearchTap: (String value) {
  Map<String, dynamic> map;
  List<dynamic> results;
  // dynamic urls; delete this line
  List<String> image_urls = <String>[];

  // does this future completes without error?
  _service.fetchPhotos(value).then((value) => {
        map = jsonDecode(value.body),
        results = map["results"],
        thumbs = results.map((e) => e["urls"]),
        for (dynamic thumb in thumbs)
          image_urls.add(thumb["thumb"].toString()),
        setState(() => urls = image_urls)
  });
},

更新

我刚刚制作了一个最小的可重现项目并毫无问题地运行它

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        theme: ThemeData.light(),
        debugShowCheckedModeBanner: false,
        home: MyHomePage());
  }
}

Future<List<String>> thumbnails(int value) {
  String url = 'https://picsum.photos/id/x/200/300'; //dummy api to retrieve pics
  List<String> myThumbs = [];
  int n = value ?? 0;

  for(int i = 0; i < n; i++) myThumbs.add(
    url.replaceFirst('x', i.toString()) //just change x for any number to retrieve a real url from the api
  );

  myThumbs.shuffle();

  return Future<List<String>>.value(myThumbs);
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<String> urls;

  @override
  void initState() {
    urls = <String>[];
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        margin: EdgeInsets.only(top: 100),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: <Widget>[
            Padding(
             padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
             child: TextField(
              key: Key('MyTextField'),
              onSubmitted: (String value) {
                thumbnails(int.tryParse(value)).then((list) {
                  setState(() => urls = list);
                });
              },
             ),
            ),
            PhotosGrid(urls: urls),
          ],
        ),
      ),
    );
  }
}

class PhotosGrid extends StatefulWidget {
  final List<String> urls;

  PhotosGrid({Key key, this.urls}) : super(key: key);
  @override
  _PhotosGridState createState() => _PhotosGridState();
}

class _PhotosGridState extends State<PhotosGrid> {
  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: Container(
        child: ListView(
          children: <Widget>[
            for (var url in widget.urls) PhotoCard(imageUrl: url)
          ],
        ),
      ),
    );
  }
}

class PhotoCard extends StatefulWidget {
  final String imageUrl;

  PhotoCard({Key key, this.imageUrl}) : super(key: key);

  @override
  _PhotoCardState createState() => _PhotoCardState();
}

class _PhotoCardState extends State<PhotoCard> {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Padding(
        padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 32),
        child: Container(
          height: 220,
          decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(5.0),
              image: DecorationImage(
                image: NetworkImage(widget.imageUrl),
                fit: BoxFit.cover,
              )),
        ),
      ),
    );
  }
}

而不是你的 SearchBar 我只是放了一个 TextField (因为我没有 SearchBar 类,但它应该工作相同)。提交后,我尝试将其解析为一个数字(请插入数字),然后从图片的虚拟 api 创建一个列表(我没有您正在使用的服务)。想法是一样的,我仍然使用你所有的类并且运行它没有问题

例子

我唯一的建议是尽量减少变量的数量(或仅在需要它们的地方创建它们),如果您已经知道您期望的值类型,请避免使用动态

onSearchTap: (String value) {

   _service.fetchPhotos(value).then((value) => {
    //declare them here if you only will use them inside the then future
     Map<String, dynamic> map = jsonDecode(value.body),
     List<dynamic> results = map["results"],
     var thumbs = results.map((e) => e["urls"]),
     List<String> image_urls = <String>[];
     for (var thumb in thumbs) image_urls.add(thumb["thumb"].toString()),
     setState(() => urls = image_urls);
  });
 },

推荐阅读