Блок Flutter с API новостей

Я сложен !! и я знаю, что найду здесь помощь. Я создаю приложение flutter, которое извлекает данные из API news.org. Все работало нормально, пока я не начал внедрять BLOC в приложение. Я успешно реализовал первую часть с BLOC с извлечением всех данных из API. Следующее, что нужно сделать, - это получить другие данные, используя категории, предоставленные API, на другой странице с помощью BLOC. Например, есть такие категории, как бизнес, технологии, финансы и т. Д. Итак, главное, когда пользователь нажимает на любую категорию, данные отображаются из API с помощью BLOC. Ниже приведены коды для блока ...
ЭТО ОШИБКА, КОТОРАЯ ПОЛУЧИЛА

════════ Исключение, обнаруженное библиотекой виджетов. ═════════════════ = Следующее утверждение было создано при построении BlocListener ‹ArticleBloc, ArticleState› (dirty, state: _BlocListenerBaseState ‹ArticleBloc, ArticleState› # aae07): функция сборки вернула значение null .

Неприятный виджет: BlocListener ‹ArticleBloc, ArticleState› Функции сборки никогда не должны возвращать null.

Чтобы вернуть пустое пространство, которое заставляет строительный виджет заполнять доступное место, верните Container (). Чтобы вернуть пустое пространство, занимающее как можно меньше места, верните Container (ширина: 0,0, высота: 0,0).

Соответствующий виджет, вызывающий ошибку: BlocListener ‹ArticleBloc, ArticleState› file: /// C: /flutter/.pub-cache/hosted/pub.dartlang.org/flutter_bloc-6.1.1/lib/src/bloc_builder.dart : 149: 12 Когда было создано исключение, это был стек: # 0 debugWidgetBuilderValue. (пакет: flutter / src / widgets / debug.dart: 302: 7) # 1 debugWidgetBuilderValue (пакет: flutter / src / widgets / debug.dart: 323: 4) # 2 ComponentElement.performRebuild (пакет: flutter / src / widgets /framework.dart:4632:7) # 3 StatefulElement.performRebuild (пакет: flutter / src / widgets / framework.dart: 4800: 11) # 4 Element.rebuild (пакет: 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,
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}

person Aristo Crat    schedule 10.12.2020    source источник


Ответы (1)


из того, что я вижу, вы можете попробовать одно из следующих решений:

  1. попробуйте в построителе блоков вернуть Контейнер, и внутри него вы укажете следующее:

        builder: (context, state) {
         return Container(
           child: Column(
             children: [
               if (state is Loading)
                 CircularProgressIndicator(),
               if (state is Loaded)
                 CatsListView(data: state.data),
    
  2. попробуйте охватить все ваши типы состояний в if/else в вашем построителе блоков, поэтому давайте предположим, что у вас есть 2 состояния (состояние1, состояние2), поэтому ваш построитель блоков будет примерно таким

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

обратите внимание, что если вы охватили все состояния, вам не нужно делать последнее еще

person Baraa Aljabban    schedule 11.12.2020
comment
да исправить . Но основная проблема здесь в том, что класс TestCat.dart, который является дочерним по отношению к home в main.dart, должен быть другой страницей, которая вызывается из onPressed на кнопке. Я положил его сюда для простоты. Основная проблема заключается в размещении BlocProvider на кнопках и вызове BlocBuilder в классе TestCat.dart. Это ссылка на проект github.com/crataristo4/news_app_using_bloc. Спасибо - person Aristo Crat; 11.12.2020