0

I am stacked!! and i know i will find help here . I create a flutter application which fetches data from news.org API . Everything was working fine until i started implementing BLOC in the app . I have successfully implemented the first part with BLOC with fetches all the data from the API . the next thing to do is to fetch another data using the categories provided by the API in another page using BLOC . For instance , there are categories like business , technology , finance etc . So main thing is when the user taps on any of the category the data show be fetched from the API using BLOC . the following are the codes for the bloc ...
THIS IS THE ERROR I GET

════════ Exception caught by widgets library ═══════════════════════════════════════════════════════ The following assertion was thrown building BlocListener<ArticleBloc, ArticleState>(dirty, state: _BlocListenerBaseState<ArticleBloc, ArticleState>#aae07): A build function returned null.

The offending widget is: BlocListener<ArticleBloc, ArticleState> Build functions must never return null.

To return an empty space that causes the building widget to fill available room, return "Container()". To return an empty space that takes as little room as possible, return "Container(width: 0.0, height: 0.0)".

The relevant error-causing widget was: BlocListener<ArticleBloc, ArticleState> file:///C:/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_bloc-6.1.1/lib/src/bloc_builder.dart:149:12 When the exception was thrown, this was the stack: #0 debugWidgetBuilderValue. (package: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) ... ════════════════════════════════════════════════════════════════════════════════════════════════════

RepositoRy

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 Answers1

1

from what I see you might try one of the below solutions :

  1. try in bloc builder to return Container and inside it handle you states like this :

        builder: (context, state) {
         return Container(
           child: Column(
             children: [
               if (state is Loading)
                 CircularProgressIndicator(),
               if (state is Loaded)
                 CatsListView(data: state.data),
    
  2. try to cover all your kind of states in if/else in your Bloc builder so let's assume that you have 2 states (state1, state2) so your Bloc builder would be something like this

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

note that if you covered all states you don't have to do the last else

Baraa Aljabban
  • 992
  • 13
  • 22