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("’", "'").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("’", "'").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("’", "'")
.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("’", "'").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("’", "'")
.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(),
),
],