Flutter Bloc 与新闻 api

Posted

技术标签:

【中文标题】Flutter Bloc 与新闻 api【英文标题】:Flutter Bloc with news 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,
                ),
              ),
            )
          ],
        ),
      ),
    );
  

【问题讨论】:

【参考方案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),
    

    尝试在 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。谢谢

以上是关于Flutter Bloc 与新闻 api的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 状态管理(BloC):无状态与有状态小部件

将flutter_bloc与tabview一起使用

如何将 Flutter Bloc 与 Firebase 电话身份验证一起使用

在 Flutter 中使用 BLoC - 在有状态小部件与无状态小部件中的使用

Flutter Bloc 与“InProgress”的提供者状态管理

Flutter Bloc 与 firebase 查询有多种关系