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 与 Firebase 电话身份验证一起使用
在 Flutter 中使用 BLoC - 在有状态小部件与无状态小部件中的使用