0

I'm trying to create list of favourite news using bloc, now if I want to add to the favourite list it does happen but if I want to remove it then list is not getting updated so it is not removing from UI.

My bloc logic,

class FavouriteBloc extends Bloc<FavouriteEvent, List<Articles>> {
  FavouriteBloc() : super(null);
  List<Articles> articles = [];
  @override
  Stream<List<Articles>> mapEventToState(FavouriteEvent event) async* {
    switch (event.eventType) {
      case EventType.add:
         articles.add(event.articles);
         yield articles;
        break;
      case EventType.delete:
        articles.remove(event.articles);
        yield articles;
        break;
    }
  }
}

event class,

enum EventType {add, delete}

class FavouriteEvent{
  Articles articles;
  EventType eventType;
  FavouriteEvent.add({this.articles,this.eventType});
  FavouriteEvent.remove({this.articles,this.eventType});
}

the UI part,

In this screen when I add to favourites it shows list of cards which I have added and then I use onTap to remove it from the list but that is not happening

class FavouriteScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var height = MediaQuery.of(context).size.height;
    var width = MediaQuery.of(context).size.width;
    return Scaffold(
      appBar: AppBar(),
      body: BlocBuilder<FavouriteBloc, List<Articles>>(
        buildWhen: (previous, current) {
          if(previous.length<current.length){
            return true;
          }
          return false;

        },
        builder: (context, newsList) {
          if (newsList == null) {
            return Center(
              child: Text(
                week7.Strings.noFav,
                style: Theme.of(context).textTheme.headline6,
              ),
            );
          }
          return ListView.builder(
              itemCount: newsList.length,
              shrinkWrap: true,
              itemBuilder: (context, index) {
                return GestureDetector(
                  onTap: () { 
                    BlocProvider.of<FavouriteBloc>(context).add(  //<--- this is how I'm trying to remove
                        FavouriteEvent.remove(
                            articles: Articles(
                                urlToImage: newsList[index].urlToImage,
                                title: newsList[index].title,
                                author: newsList[index].author
                            ),
                            eventType: EventType.delete));
                  },
                  child: Card(...),
                );
              });
        },
      ),
    );
  }
}

model class,

@JsonSerializable()
class Articles {
  Source source;
  String author;
  String title;
  String description;
  String url;
  String urlToImage;
  DateTime publishedAt;
  String content;
  Articles({
    this.source,
    this.author,
    this.title,
    this.description,
    this.url,
    this.urlToImage,
    this.publishedAt,
    this.content,
  });

  factory Articles.fromJson(Map<String, dynamic> json) =>
      _$ArticlesFromJson(json);
}

so can anyone tell me what I'm doing wrong here?

Pokaboom
  • 1,110
  • 9
  • 28

2 Answers2

2
hi bro add this lib 
https://pub.dev/packages/equatable 

@JsonSerializable()
class Articles  extends Equatable{
  Source source;
  String author;
  String title;
  String description;
  String url;
  String urlToImage;
  DateTime publishedAt;
  String content;
  Articles({
    this.source,
    this.author,
    this.title,
    this.description,
    this.url,
    this.urlToImage,
    this.publishedAt,
    this.content,
  });

 @override
  List<Object> get props => [name];//  depending on which field you want to remove the list item, replace "name" with your field.

  factory Articles.fromJson(Map<String, dynamic> json) =>
      _$ArticlesFromJson(json);
}
gowthaman C
  • 472
  • 6
  • 16
0

Dart compares if the two objects are the same instance. You need to override == operator or use library like equatable.

The first thing you need to do is to delete buildWhen. Right now it will only update(rebuild) when you add items but not when you remove them.

        buildWhen: (previous, current) {
          if(previous.length<current.length){
            return true;
          }
          return false;

        },

Use State class to represent the state because the list is always the same and it will not rebuild. After that adjust your widget code to use state.articles.

class FavouriteState {
 final List<Articles> articles;
 FavouriteState(this.artticles);
}

class FavouriteBloc extends Bloc<FavouriteEvent, FavouriteState> {
  FavouriteBloc() : super(null);
  List<Articles> _articles = [];
  @override
  Stream<FavouriteState> mapEventToState(FavouriteEvent event) async* {
    switch (event.eventType) {
      case EventType.add:
         _articles.add(event.articles);
         yield FavouriteState(_articles);
        break;
      case EventType.delete:
        _articles.remove(event.articles);
        yield FavouriteState(_articles);
        break;
    }
  }
}

Example comparing urlToImage, title and author

@JsonSerializable()
class Articles {
  Source source;
  String author;
  String title;
  String description;
  String url;
  String urlToImage;
  DateTime publishedAt;
  String content;
  Articles({
    this.source,
    this.author,
    this.title,
    this.description,
    this.url,
    this.urlToImage,
    this.publishedAt,
    this.content,
  });

    @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Articles && runtimeType == other.runtimeType && urlToImage == other.urlToImage &&
      title == other.title && author == other.author;

  @override
  int get hashCode => urlToImage.hashCode ^ title.hashCode ^ author.hashCode;

  factory Articles.fromJson(Map<String, dynamic> json) =>
      _$ArticlesFromJson(json);
}

Example using Equatable package

@JsonSerializable()
class Articles  extends Equatable{
  Source source;
  String author;
  String title;
  String description;
  String url;
  String urlToImage;
  DateTime publishedAt;
  String content;
  Articles({
    this.source,
    this.author,
    this.title,
    this.description,
    this.url,
    this.urlToImage,
    this.publishedAt,
    this.content,
  });

 @override
  List<Object> get props => [author, title, description, url, urlToImage, content];

  factory Articles.fromJson(Map<String, dynamic> json) =>
      _$ArticlesFromJson(json);
}
YoBo
  • 2,292
  • 3
  • 9
  • 25
  • ok that works but it does rebuild after state change so is my condition in `buildWhen` is right or I need to do something else – Pokaboom Mar 03 '21 at 05:52
  • Rebuilding is normal when the state changes. Your condition is when the size of the list changes, rebuild. Adding or removing items from the list will trigger rebuild and that is the normal behavior. If it does not rebuild, than you will not see the changed articles. – YoBo Mar 03 '21 at 05:57
  • tbh you need to remove `buildWhen`. You want the rebuild to be triggered every time you add or remove item from the list. No point in that condition there. Right now it will rebuild only when you add items, not when you remove them. – YoBo Mar 03 '21 at 05:59
  • sry I forgot the 'not', It does not rebuild – Pokaboom Mar 03 '21 at 05:59
  • It will not rebuild if you remove item. Right? That's coming from the `buildWhen`. Remove `buildWhen` and it will be fine. – YoBo Mar 03 '21 at 06:01
  • can you share the state class – gowthaman C Mar 03 '21 at 06:25
  • That's because you are usingn List as state. try the following in the BLoC: `yield articles.toList();` for add and remove to create a new List. The bloc will not rebuild if the prev state is the same as the new one. – YoBo Mar 03 '21 at 06:32
  • Updated my code with example on how to create state that holds `articles`. I suggest you to move in that direction. – YoBo Mar 03 '21 at 06:37