0

I'm currently trying to use Provider to save articles once user tap on bookmark icon, new articles once tapped should show up on favourite page, I don't understand why it doesn't save anything. Below my code used. What do I wrong?

ArticleModel

class ArticleModel with ChangeNotifier{
  int?          id;
  String?       urlImage;
  String?       urlImageSource;
  String?       title;
  final String? description;
  String?       link;
  bool          isFavorite;


  ArticleModel({
             this.id,
    required this.urlImage,
    required this.urlImageSource,
    required this.title,
    required this.description,
    required this.link,
             this.isFavorite = false,
  });

  factory ArticleModel.fromJson(Map<String, dynamic> parsedJson) => ArticleModel(
    id:             parsedJson["id"],
    urlImage:       parsedJson["_embedded"]["wp:featuredmedia"][0]["link"],
    urlImageSource: parsedJson["_embedded"]['wp:featuredmedia'][0]["media_details"]["sizes"]["thumbnail"]["source_url"],
    title:          parsedJson["title"]["rendered"].replaceAll("&#8217;", "'").replaceAll("<p>", "").replaceAll("</p>", ""),
    description:    parsedJson['content']['rendered'],
    link:           parsedJson['link'],
    isFavorite:     parsedJson['isFavorite'],
  );

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['id']             = id;
    data['urlImage']       = urlImage;
    data['urlImageSource'] = urlImageSource;
    data['title']          = title;
    data['description']    = description;
    data['link']           = link;
    data['isFavorite']     = isFavorite;

    return data;
  }
}

Article page where user can read and tap on bookmark icon to save article

class ArticlePage extends StatefulWidget {
  final id;
  final urlImage;
  final title;
  final description;
  const ArticlePage({Key? key, required this.data, this.id, this.urlImage, this.title, this.description}) : super(key: key);
  final data;

  @override
  _ArticlePageState createState() => _ArticlePageState(data, id, urlImage, title, description);
}

class _ArticlePageState extends State<ArticlePage> {
  final id;
  final urlImage;
  final title;
  final description;
  final data;
  _ArticlePageState(this.data, this.id, this.urlImage, this.title, this.description);
  /// Save article
  /// Last test
  late FavoriteArticles _favoriteArticles;
  late bool _favoriteArticlesReady = false;
  late bool isFavorite = false;
  /// Save articles
  @override
  void initState() {
    super.initState();
    _favoriteArticles = Provider.of<FavoriteArticles>(context, listen: false,);
    setState(() {
      _favoriteArticlesReady = true;
      isFavorite = _favoriteArticles.isFavorite(widget.id); // initialize isFavorite here
    });
  }

  



  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _favoriteArticles = Provider.of<FavoriteArticles>(context, listen: false,);
  }

  /// Add article to favorites
  void addFavorite(ArticleModel id) {
    _favoriteArticles.addFavorite(
      id: widget.id,
      urlImage: widget.urlImage,
      title: widget.title,
    );
  }
  /// Add article from favorites
  void removeFavorite(ArticleModel id) {
    var _favoriteArticleIds;
    Provider.of<FavoriteArticles>(context, listen: false);
    _favoriteArticleIds.remove(
      id: widget.id,
      urlImage: widget.urlImage,
      title: widget.title,
    );
  }

  Future<void> savedArticle() async {
    if (!_favoriteArticlesReady) {
      return;
    }
    final isFavorite = _favoriteArticles.isFavorite(widget.id);

    setState(() {
      _favoriteArticles = Provider.of<FavoriteArticles>(context, listen: false,);
      if (!isFavorite) {
        _favoriteArticles.removeFavorite(widget.id);
      } else {
        _favoriteArticles.addFavorite(
          id: widget.id,
          urlImage: widget.urlImage,
          title: widget.title,
        );
        print("Saved article "
            "${data["_embedded"]["wp:featuredmedia"][0]["link"]}   " // Image
            "Article link "
            "${data["link"]}" // Link
            "${data["title"]["rendered"].toString().replaceAll("<p>", "").replaceAll("&#8217;", "'").replaceAll("</p>", "")}", // Title
        );
      }
    });
  }


  @override
  Widget build(BuildContext context) {

    return Scaffold(
      backgroundColor: Colors.white,
      body: CustomScrollView(
        slivers: <Widget>[
          SliverAppBar(
            leading: Builder(
              builder: (BuildContext context) {
                return GestureDetector(
                  child: Center(
                    child: Container(
                      color: Colors.white70.withOpacity(0),
                      child: Container(
                        height: 30,
                        width: 30,
                        decoration: const BoxDecoration(
                          image: DecorationImage(
                            image: AssetImage(
                              'assets/images/previous.png',
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                  onTap: () {
                    Navigator.of(context).pop();
                  },
                );
              },
            ),
            flexibleSpace: FlexibleSpaceBar(
              centerTitle: true,
              background: CachedNetworkImage(
                imageUrl: data["_embedded"]["wp:featuredmedia"][0]["link"],
                //articleModel.urlImage!,
                fit: BoxFit.cover,
                placeholder: (context, url) => Image.asset("assets/gif/shimmer.gif", fit: BoxFit.cover,),
                errorWidget: (context, url, error) => Image.asset("assets/images/unloadedImage.png", width: 250, height: 250),
              ),
            ),
            actions: [
              /// Favorite icon
              Padding(
                  padding: const EdgeInsets.only(right: 10.0),
                  child: Container(
                    margin: const EdgeInsets.only(top: 12.0, bottom: 10.0),
                    width: 35,
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(50),
                      color: Colors.white,
                    ),
                    child: IconButton(
                      icon: isFavorite
                          ? const Icon(Icons.bookmark_outline_outlined, color: Color(0xFF0D47A1), size: 18,)
                          : const Icon(Icons.bookmark_outlined, color: Color(0xFF0D47A1), size: 18,),
                      onPressed:
                      //savedArticle,

                          () async {
                        await savedArticle();
                      },
                    ),
                  ),
              ),
              /// Share article icon
              Padding(
                  padding: const EdgeInsets.only(right:15.0),
                  child:  IconButton(
                    icon: Image.asset('assets/images/share_icon.png',),
                    onPressed: () {
                      Share.share(data["link"], subject: "Leggi l'articolo di AssoFacile");
                    },
                  ),
              ),
            ],
            floating: true,
            expandedHeight: 320,
          ),
          SliverList(
            delegate: SliverChildListDelegate([
              Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  const SizedBox(height: 20,),
                  Container(
                    padding: const EdgeInsets.all(16),
                    child:
                    Text(data["title"]["rendered"]
                        .toString()
                        .replaceAll("<p>", "")
                        .replaceAll("&#8217;", "'")
                        .replaceAll("</p>", ""),
                      style: const TextStyle(
                        fontWeight: FontWeight.w600,
                        fontSize: 20,
                        fontFamily: "Raleway",
                      ),
                    ),
                  ),
                  const SizedBox(height: 0),
                  Container(
                    padding: const EdgeInsets.all(16),
                    child: HtmlWidget(
                      data['content']['rendered'].toString().replaceAll("<p>", "").replaceAll("&#8217;", "'").replaceAll("</p>", ""),
                    ),
                  ),
                  const SizedBox(height: 20),
                ],
              ),
            ],
            ),
          ),
        ],
      ),
    );
  }
}

Favourite Article extends ChangeNotifier

class FavoriteArticles extends ChangeNotifier {
  static const String _favoriteArticleIdsKey = 'favoriteArticleIds';
  final List<int> _favoriteArticleIds = [];
  List<int> get favoriteArticleIds => _favoriteArticleIds;

  void addFavorite({required int id, required String urlImage, required String title}) {
    if (!_favoriteArticleIds.contains(id)) {
      _favoriteArticleIds.add(id);
      notifyListeners();
      saveToSharedPreferences();
    }
  }

  void removeFavorite(int id) {
    if (_favoriteArticleIds.contains(id)) {
      _favoriteArticleIds.remove(id);
      notifyListeners();
      saveToSharedPreferences();
    }
  }

  void toggleFavorite(int id, String urlImage, String title) {
    if (_favoriteArticleIds.contains(id)) {
      _favoriteArticleIds.remove(id);
    } else {
      _favoriteArticleIds.add(id);
    }
    notifyListeners();
    saveToSharedPreferences();
  }

  bool isFavorite(int id) {
    return _favoriteArticleIds.contains(id);
  }

  Future<void> saveToSharedPreferences() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setStringList(_favoriteArticleIdsKey, _favoriteArticleIds.map((id) => id.toString()).toList());
  }

  Future<void> loadFromSharedPreferences() async {
    final prefs = await SharedPreferences.getInstance();
    final favoriteArticleIdsString = prefs.getStringList(_favoriteArticleIdsKey);
    if (favoriteArticleIdsString != null) {
      _favoriteArticleIds.clear();
      _favoriteArticleIds.addAll(favoriteArticleIdsString.map((id) => int.parse(id)));
      notifyListeners();
    }
  }
}

Favourite page where saved articles should show up

class FavoritePage extends StatefulWidget {
  final data;
  const FavoritePage({Key? key, this.data}) : super(key: key);
  @override
  _FavoritePageState createState() => _FavoritePageState();
}

class _FavoritePageState extends State<FavoritePage> {
  late List data;

  @override
  void initState() {
    super.initState();
    data = widget.data;
  }

  @override
  Widget build(BuildContext context) {
    final isFavorite = Provider.of<FavoriteArticles>(context);

    return Scaffold(
        appBar: AppBar(
          backgroundColor: assofacileMainColor,
          centerTitle: true,
          elevation: 0,
          leading: Builder(
            builder: (BuildContext context) {
              return IconButton(
                icon: const Icon(
                  Icons.arrow_back_ios_rounded,
                  color: Colors.white,
                ),
                onPressed: () {
                  Navigator.of(context).pop();
                },
                tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
              );
            },
          ),
          title: const Text("Preferiti",
            style: TextStyle(
                color: Colors.white,
                fontFamily: "Raleway",
                fontSize: 18,
                fontWeight: FontWeight.w600),
          ),
        ),
      body: Consumer<FavoriteArticles>(
        builder: (BuildContext, favoriteArticles, _) {
          final articleIds = favoriteArticles.favoriteArticleIds;

          if (data == null || data.isEmpty) {
            return const EmptyArticle();
          }
          else if (data.isNotEmpty) {
            return ListView.builder(
              itemCount: articleIds.length,
              itemBuilder: (context, index) {
                final id = articleIds[index];
                return Card(
                  margin: const EdgeInsets.all(8),
                  elevation: 5,
                  shadowColor: Colors.black26,
                  color: Colors.white,
                  child: InkWell(
                    child: ClipRRect(
                      borderRadius: BorderRadius.circular(10),
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          SizedBox(
                            height: 190,
                            width: double.infinity,
                            child:
                            Image(
                              image: AdvancedNetworkImage(
                                data[index]["_embedded"]['wp:featuredmedia'][0]["media_details"]["sizes"]["medium"]["source_url"],
                                useDiskCache: true,
                                cacheRule: const CacheRule(maxAge: Duration(days: 1)),
                              ),
                              fit: BoxFit.cover,
                            ),
                          ),
                          // Title article
                          Column(
                            children: [
                              Padding(
                                padding: const EdgeInsets.only(left: 16, top: 16, bottom: 16),
                                child: Row(
                                  children: [
                                    Expanded(
                                      child: Text(
                                        data[index]["title"]["rendered"]
                                            .replaceAll("&#8217;", "'")
                                            .replaceAll("<p>", "")
                                            .replaceAll("</p>", ""),
                                        style: const TextStyle(
                                          fontSize: 18,
                                          fontWeight: FontWeight.w600,
                                          fontFamily: "Raleway",
                                        ),
                                        overflow: TextOverflow.ellipsis,
                                        maxLines: 2,
                                        //softWrap: false,
                                      ),
                                    ),
                                  ],
                                ),
                              )
                            ],
                          ),
                        ],
                      ),
                    ),
                    onTap: () {
                      var articleData = data[index];
                      //var snapshot;
                      Navigator.push(context,
                        MaterialPageRoute(
                          builder: (context) =>
                              ArticlePage(
                                  data: articleData
                                  //data: snapshot.data?[index]
                              ),
                        ),
                      );
                    },
                  ),
                );
              },
            );
          }
          return const EmptyArticle();
        },
      ),
    );
  }
}


Provider in my MyApp page

providers: [
          ChangeNotifierProvider<FavoriteArticles>(
            create: (context) => FavoriteArticles(),
          ),
        ],

Pimpi Rimpà
  • 75
  • 1
  • 8

1 Answers1

1

You are calling notifyListeners() before saving entities. So, try to use this approach:

await saveToSharedPreferences();
notifyListeners();

Don't call any updates before you don't save your data. In some cases you need more time and entities can save after UI refresh.

fartem
  • 2,361
  • 2
  • 8
  • 20