Save favorite posts in Local with provider in Flutter

375 Views Asked by At

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(),
          ),
        ],

1

There are 1 best solutions below

1
fartem On

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.