【问题标题】:Flutter Bloc with news apiFlutter Bloc 与新闻 api
【发布时间】:2021-03-22 01:20:10
【问题描述】:

我被堆积了!!我知道我会在这里找到帮助。我创建了一个从 news.org API 获取数据的颤振应用程序。一切正常,直到我开始在应用程序中实现 BLOC。我已经成功地使用 BLOC 实现了第一部分,并从 API 获取所有数据。接下来要做的是使用 BLOC 在另一个页面中使用 API 提供的类别获取另一个数据。 例如,有商业、技术、金融等类别。所以主要的是当用户点击任何类别时,数据显示是使用 BLOC 从 API 获取的。 以下是该集团的代码...
这是我得到的错误

════════小部件库捕获的异常═════════════════════════════════════ ═════════════════════ 在构建 BlocListener(dirty, state: _BlocListenerBaseState#aae07) 时抛出了以下断言: 构建函数返回 null。

有问题的小部件是:BlocListener 构建函数绝不能返回 null。

要返回导致建筑小部件填满可用空间的空白空间,请返回“Container()”。要返回占用尽可能少空间的空白空间,请返回“Container(width: 0.0, height: 0.0)”。

相关的导致错误的小部件是: BlocListener file:///C:/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_bloc-6.1.1/lib/src/bloc_builder.dart:149:12 抛出异常时,这是堆栈: #0 调试小部件生成器值。 (包:flutter/src/widgets/debug.dart:302:7) #1 debugWidgetBuilderValue (package:flutter/src/widgets/debug.dart:323:4) #2 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4632:7) #3 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4800:11) #4 Element.rebuild (package:flutter/src/widgets/framework.dart:4343:5) ... ══════════════════════════════════════════════════ ══════════════════════════════════════════════════

存储库

abstract class CategoryRepository {
  Future<List<Article>> getCategory(String category);
}

class CatService implements CategoryRepository {
  @override
  Future<List<Article>> getCategory(String category) async {
    //  List<Article> categoryNewsList = [];

    String url =
        "http://newsapi.org/v2/top-headlines?country=us&category=$category&apiKey=df74fc47f0dd401bb5e56c34893a7795";
    return getData(url);

    /*var response = await http.get(url);

    //decode the response into a json object
    var jsonData = jsonDecode(response.body);

    //check if the status of the response is OK
    if (jsonData["status"] == "ok") {
      jsonData["articles"].forEach((item) {
//check if the imageUrl and description are not null
        if (item["urlToImage"] != null && item["description"] != null) {
          //create an object of type NewsArticles
          Article newsArticleModel = new Article(
              author: item["author"],
              title: item["title"],
              description: item["description"],
              url: item["url"],
              urlToImage: item["urlToImage"],
              content: item["content"]);

          //add data to news list
          categoryNewsList.add(newsArticleModel);
        }
      });
    }

    return categoryNewsList;*/
  }
}

Future<List<Article>> getData(String url) async {
  List<Article> items = [];

  var response = await http.get(url);

  //decode the response into a json object
  var jsonData = jsonDecode(response.body);

  //check if the status of the response is OK
  if (jsonData["status"] == "ok") {
    jsonData["articles"].forEach((item) {
//check if the imageUrl and description are not null
      if (item["urlToImage"] != null && item["description"] != null) {
        //create an object of type NewsArticles
        Article article = new Article(
            author: item["author"],
            title: item["title"],
            description: item["description"],
            url: item["url"],
            urlToImage: item["urlToImage"],
            content: item["content"]);

        //add data to news list
        items.add(article);
      }
    });
  }

  return items;
}
Bloc
class ArticleBloc extends Bloc<ArticleEvent, ArticleState> {
  CategoryRepository categoryRepository;

  ArticleBloc({this.categoryRepository}) : super(ArticleInitial());

  @override
  Stream<ArticleState> mapEventToState(
    ArticleEvent event,
  ) async* {
    if (event is GetArticle) {
      try {
        yield ArticleLoading();
        final articleList =
            await categoryRepository.getCategory(event.category);
        yield ArticleLoaded(articleList);
      } catch (e) {
        print(e.message);
      }
    }
  }
}

Event
class GetArticle extends ArticleEvent{
  final String category;

  GetArticle(this.category);
}

States
@immutable
abstract class ArticleState  {
  const ArticleState();
}

class ArticleInitial extends ArticleState {
  const ArticleInitial();
}

class ArticleLoading extends ArticleState {
 const ArticleLoading();
}

class ArticleLoaded extends ArticleState {
    final List<Article> articleList;

  ArticleLoaded(this.articleList);
}

class ArticleError extends ArticleState {
  final String error;

  ArticleError(this.error);

  @override
  bool operator ==(Object object) {
    if (identical(this, object)) return true;

    return object is ArticleError && object.error == error;
  }


  @override
  int get hashCode => error.hashCode;

}

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'News app',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: BlocProvider(
        child: TestCat(),
        create: (context) => ArticleBloc(categoryRepository : CatService()),
      ),
    );
  }
}

tEST category page

class TestCat extends StatefulWidget {
  @override
  _TestCatState createState() => _TestCatState();
}

class _TestCatState extends State<TestCat> {
  bool isLoading = true;

  List<String> categoryItems;

  @override
  void initState() {
    super.initState();

    categoryItems = getAllCategories();
    // getCategory(categoryItems[0]);

    // getCategoryNews();
  }

  getCategory(cat) async {
    context.bloc<ArticleBloc>().add(GetArticle(cat));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: header(context, isAppTitle: false, title: "App"),
      body: _newsBody(context),
    );
  }

  _newsBody(context) {
    return ListView(
      children: [
        //category list
        Container(
          padding:
              EdgeInsets.symmetric(horizontal: NewsAppConstants().margin16),
          height: NewsAppConstants().columnHeight70,
          child: ListView.builder(
            itemCount: categoryItems.length,
            itemBuilder: (context, index) {
              return TitleCategory(
                title: categoryItems[index],
                onTap: ()=> callCat(context, categoryItems[index]),
              );
            },
            shrinkWrap: true,
            scrollDirection: Axis.horizontal,
          ),
        ),

        Divider(),

           Container(
          child: BlocBuilder<ArticleBloc, ArticleState>(
             builder: (context, ArticleState articleState) {
            //check states and update UI
            if (articleState is ArticleInitial) {
              return buildInput(context);
            } else if (articleState is ArticleLoading) {
              return Loading();
            } else if (articleState is ArticleLoaded) {
              List<Article>  articles = articleState.articleList;
              updateUI(articles);
            } else if (articleState is ArticleError) {
              // shows an error widget when something goes wrong
              final error = articleState.error;
              final errorMsg = "${error.toString()}\nTap to retry";
              ShowErrorMessage(
                errorMessage: errorMsg,
                onTap: getCategory,
              );
            }
            return buildInput(context);
          }),
        ),
      ],
    );
  }

  getAllCategories() {
    List<String> categoryList = [
      "Business",
      "Entertainment",
      "General",
      "Sports",
      "Technology",
      "Health",
      "Science"
    ];
    return categoryList;
  }

  Widget updateUI(List<Article> newsList) {
    return SingleChildScrollView(
        child: Column(
      children: [
        Container(
          child: ListView.builder(
              physics: ClampingScrollPhysics(),
              shrinkWrap: true,
              itemCount: newsList.length,
              itemBuilder: (context, index) {
                return NewsBlogTile(
                  urlToImage: newsList[index].urlToImage,
                  title: newsList[index].title,
                  description: newsList[index].description,
                  url: newsList[index].url,
                );
              }),
        ),
        Divider(),
      ],
    ));
  }

  buildInput(context) {
    ListView.builder(
      itemCount: categoryItems.length,
      itemBuilder: (context, index) {
        return TitleCategory(
          title: categoryItems[index],
          onTap: () {
            print("tapped");
           // callCat(context, categoryItems[index]);
          },
        );
      },
      shrinkWrap: true,
      scrollDirection: Axis.horizontal,
    );
  }

  callCat(BuildContext context, String cat) {
    print(cat);
    context.bloc<ArticleBloc>().add(GetArticle(cat));
  }
}


//this displays the data fetched from the API
class NewsBlogTile extends StatelessWidget {
  final urlToImage, title, description, url;

  NewsBlogTile(
      {@required this.urlToImage,
      @required this.title,
      @required this.description,
      @required this.url});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {},
      child: Expanded(
        flex: 1,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Container(
              margin: EdgeInsets.all(NewsAppConstants().margin8),
              child: Column(
                children: <Widget>[
                  ClipRRect(
                      borderRadius:
                          BorderRadius.circular(NewsAppConstants().margin8),
                      child: Image.network(urlToImage)),
                  Text(
                    title,
                    style: TextStyle(
                        fontWeight: FontWeight.w600,
                        color: Colors.black,
                        fontSize: NewsAppConstants().margin16),
                  ),
                  SizedBox(
                    height: NewsAppConstants().margin8,
                  ),
                  Text(
                    description,
                    style: TextStyle(color: Colors.black54),
                  )
                ],
              ),
            ),
            Divider(),
          ],
        ),
      ),
    );
  }
}



//news title category
class TitleCategory extends StatelessWidget {
  final title;
  final Function onTap;

  TitleCategory({this.title, this.onTap});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => onTap,
      child: Container(
        margin: EdgeInsets.all(NewsAppConstants().margin8),
        child: Stack(
          children: <Widget>[
            ClipRRect(
              borderRadius: BorderRadius.circular(NewsAppConstants().margin8),
              child: Container(
                child: Text(
                  title,
                  style: TextStyle(
                      color: Colors.white,
                      fontSize: NewsAppConstants().font16,
                      fontWeight: FontWeight.w500),
                ),
                alignment: Alignment.center,
                width: NewsAppConstants().imageWidth120,
                height: NewsAppConstants().imageHeight60,
                decoration: BoxDecoration(
                  borderRadius:
                      BorderRadius.circular(NewsAppConstants().margin8),
                  color: Colors.black,
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}

【问题讨论】:

    标签: android flutter dart bloc flutter-bloc


    【解决方案1】:

    据我所知 您可以尝试以下解决方案之一:

    1. 尝试在 bloc builder 中返回 Container 并在其中处理您的状态,如下所示:

          builder: (context, state) {
           return Container(
             child: Column(
               children: [
                 if (state is Loading)
                   CircularProgressIndicator(),
                 if (state is Loaded)
                   CatsListView(data: state.data),
      
    2. 尝试在 Bloc 构建器中的 if/else 中覆盖所有您的状态 所以让我们假设你有 2 个状态(state1,state2)所以你的 Bloc builder 会是这样的

         builder: (context, state) {
           if (state is state1) return Container();
           else if (state is state2) return Container();
           else return Container();
      

    请注意,如果您涵盖了所有州,则不必执行最后一个 else

    【讨论】:

    • 是的,正确的。但这里的主要问题是 TestCat.dart 类是 main.dart 中 home 的子类,假设是另一个页面,它是从按钮上的 onPressed 调用的。为了简单起见,我把它放在那里。主要问题是将 BlocProvider 包装在按钮上并在 TestCat.dart 类中调用 BlocBuilder。这是项目的链接github.com/crataristo4/news_app_using_bloc。谢谢
    猜你喜欢
    • 2021-03-19
    • 2020-10-10
    • 2021-01-19
    • 2019-05-23
    • 2020-02-27
    • 2019-06-28
    • 1970-01-01
    • 2022-07-10
    • 2021-03-15
    相关资源
    最近更新 更多