Skip to content

Commit

Permalink
Remove persistence of news articles to achieve easy update and delete…
Browse files Browse the repository at this point in the history
… possibility (#661)

* Remove persistence of news articles to achieve easy update and delete possibility

* Remove unused import
  • Loading branch information
adeveloper-wq authored Aug 12, 2024
1 parent dbc5af1 commit 318eada
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 108 deletions.
16 changes: 7 additions & 9 deletions lib/news/models/article.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// News-article object
class Article {
/// Primary key of the article
final int _id;
final int id;

/// Body of the article
final String text;
Expand All @@ -16,17 +16,15 @@ class Article {
final int? categoryId;

/// MD5 hash of the article
final String _md5;
final String md5;

const Article(
{required id,
{required this.id,
required this.text,
required this.title,
required this.pubDate,
required this.categoryId,
required md5})
: _id = id,
_md5 = md5;
required this.md5});

/// Returns an article given a [json] representation of an article.
factory Article.fromJson(Map<String, dynamic> json) {
Expand All @@ -42,13 +40,13 @@ class Article {

/// Returns a json representation of the article object calling this method.
Map<String, Object?> toJson() =>
{'id': _id, 'title': title, 'text': text, 'pub_date': pubDate.toString(), 'category_id': categoryId, 'md5': _md5};
{'id': id, 'title': title, 'text': text, 'pub_date': pubDate.toString(), 'category_id': categoryId, 'md5': md5};

@override
int get hashCode => _md5.hashCode;
int get hashCode => md5.hashCode;

@override
bool operator ==(Object other) {
return other is Article && other._md5 == _md5;
return other is Article && other.md5 == md5;
}
}
115 changes: 16 additions & 99 deletions lib/news/services/news.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'dart:async';
import 'dart:collection';
import 'dart:convert';

import 'package:flutter/foundation.dart' hide Category;
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'package:priobike/http.dart';
import 'package:priobike/logging/logger.dart';
import 'package:priobike/main.dart';
Expand All @@ -27,43 +27,30 @@ class News with ChangeNotifier {
List<Article> articles = [];

/// List with all articles that have been read by the user
Set<Article> readArticles = {};
HashSet<Article> readArticles = HashSet();

/// Map with all categories
Map<int, Category> categories = {};

/// Reset the service to its initial state.
Future<void> reset() async {
hasLoaded = false;
articles = [];
readArticles = {};
categories = {};
articles.clear();
readArticles.clear();
categories.clear();
notifyListeners();
}

/// Returns all available articles from the shared preferences or if not stored locally from the backend server.
Future<void> getArticles() async {
// Get articles that are already saved in the shared preferences on the device.
List<Article> localSavedArticles = await _getStoredArticles();

// If there are articles saved already in the shared preferences on the device
// get the lastSyncDate for later usage eg when deciding whether the "Neu"-tag
// should be shown on the article items in the list.
DateTime? newLastSyncDate;
if (localSavedArticles.isNotEmpty) {
newLastSyncDate = localSavedArticles[0].pubDate;
}

final settings = getIt<Settings>();

String baseUrl = settings.city.selectedBackend(false).path;

final newsArticlesUrl = newLastSyncDate == null
? "https://$baseUrl/news-service/news/articles"
: "https://$baseUrl/news-service/news/articles?from=${DateFormat('yyyy-MM-ddTH:mm:ss').format(newLastSyncDate)}Z";
final newsArticlesUrl = "https://$baseUrl/news-service/news/articles";
final newsArticlesEndpoint = Uri.parse(newsArticlesUrl);

List<Article> articlesFromServer = [];
List<Article> newArticles = [];

// Catch the error if there is no connection to the internet.
try {
Expand All @@ -77,7 +64,7 @@ class News with ChangeNotifier {
await json.decode(response.body).forEach(
(element) {
final Article article = Article.fromJson(element);
articlesFromServer.add(article);
newArticles.add(article);
},
);
hadError = false;
Expand All @@ -87,12 +74,10 @@ class News with ChangeNotifier {
hadError = true;
}

articles = [...articlesFromServer, ...localSavedArticles];
articles = newArticles;

await _getCategories();

await _storeArticles();

readArticles = await _getStoredReadArticles();

hasLoaded = true;
Expand All @@ -103,22 +88,13 @@ class News with ChangeNotifier {
Future<void> _getCategories() async {
for (final article in articles) {
if (article.categoryId != null && !categories.containsKey(article.categoryId)) {
await _getCategory(article.categoryId!);
await _fetchCategory(article.categoryId!);
}
}
}

/// Gets single category given the [categoryId] from the shared preferences or if not stored locally from the backend server
Future<void> _getCategory(int categoryId) async {
Category? category = await _getStoredCategory(categoryId);
if (category != null) {
if (!categories.containsKey(categoryId)) {
categories[categoryId] = category;
}
return;
}

// If the category doesn't exist already in the shared preferences get it from backend server.
/// Fetches single category given the [categoryId] from the backend server
Future<void> _fetchCategory(int categoryId) async {
final settings = getIt<Settings>();
final baseUrl = settings.city.selectedBackend(false).path;
final newsCategoryUrl = "https://$baseUrl/news-service/news/category/${categoryId.toString()}";
Expand All @@ -133,76 +109,17 @@ class News with ChangeNotifier {
throw Exception(err);
}

category = Category.fromJson(json.decode(response.body));
final category = Category.fromJson(json.decode(response.body));

if (!categories.containsKey(categoryId)) {
categories[categoryId] = category;
}

await _storeCategory(category);
} catch (e) {
final hint = "Failed to load category: $e";
log.e(hint);
}
}

/// Store all articles in shared preferences.
Future<void> _storeArticles() async {
if (articles.isEmpty) return;
final storage = await SharedPreferences.getInstance();

final backend = getIt<Settings>().city.selectedBackend(false);

final jsonStr = jsonEncode(articles.map((e) => e.toJson()).toList());
await storage.setString("priobike.news.articles.${backend.name}", jsonStr);
}

/// Store category in shared preferences.
Future<void> _storeCategory(Category category) async {
if (articles.isEmpty) return;
final storage = await SharedPreferences.getInstance();

final backend = getIt<Settings>().city.selectedBackend(false);

final String jsonStr = jsonEncode(category.toJson());
await storage.setString("priobike.news.categories.${backend.name}.${category.id}", jsonStr);
}

/// Get all stored articles
Future<List<Article>> _getStoredArticles() async {
final storage = await SharedPreferences.getInstance();

final backend = getIt<Settings>().city.selectedBackend(false);

final storedArticlesStr = storage.getString("priobike.news.articles.${backend.name}");

if (storedArticlesStr == null) {
return [];
}

List<Article> storedArticles = [];
for (final articleMap in jsonDecode(storedArticlesStr)) {
storedArticles.add(Article.fromJson(articleMap));
}

return storedArticles;
}

/// Get stored category for given [categoryId]
Future<Category?> _getStoredCategory(int categoryId) async {
final storage = await SharedPreferences.getInstance();

final backend = getIt<Settings>().city.selectedBackend(false);

final storedCategoryStr = storage.getString("priobike.news.categories.${backend.name}.$categoryId");

if (storedCategoryStr == null) {
return null;
}

return Category.fromJson(jsonDecode(storedCategoryStr));
}

/// Store all read articles in shared preferences.
Future<void> _storeReadArticles() async {
if (readArticles.isEmpty) return;
Expand All @@ -216,18 +133,18 @@ class News with ChangeNotifier {
}

/// Get stored articles that were already read by the user.
Future<Set<Article>> _getStoredReadArticles() async {
Future<HashSet<Article>> _getStoredReadArticles() async {
final storage = await SharedPreferences.getInstance();

final backend = getIt<Settings>().city.selectedBackend(false);

final storedReadArticlesStr = storage.getString("priobike.news.read_articles.${backend.name}");

if (storedReadArticlesStr == null) {
return {};
return HashSet();
}

Set<Article> storedReadArticles = {};
HashSet<Article> storedReadArticles = HashSet();
for (final articleMap in jsonDecode(storedReadArticlesStr)) {
storedReadArticles.add(Article.fromJson(articleMap));
}
Expand Down

0 comments on commit 318eada

Please sign in to comment.