Navigation Menu

Skip to content

Commit

Permalink
Add news articles fetching from local storage/file in catch.
Browse files Browse the repository at this point in the history
  • Loading branch information
PrimeTimeTran committed Jun 11, 2023
1 parent 4fcf081 commit 2256ec7
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 60 deletions.
1 change: 1 addition & 0 deletions assets/assets/news.json

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions lib/data/cubits/local_storage_cubit.dart
@@ -0,0 +1,11 @@
import 'package:rse/data/models/all.dart';

class LocalStorageState {
final Portfolio portfolio;
final List<Article> articles;

LocalStorageState({
required this.portfolio,
required this.articles,
});
}
1 change: 0 additions & 1 deletion lib/data/cubits/news_cubit.dart
Expand Up @@ -11,7 +11,6 @@ class NewsCubit extends Cubit<List<Article>> {
Future<void> fetchArticles() async {
try {
final articles = await _newsService.fetchArticles();
print(articles);
emit(articles);
} catch (error) {
print('Error fetching articles: $error');
Expand Down
25 changes: 25 additions & 0 deletions lib/data/models/news.dart
Expand Up @@ -40,6 +40,27 @@ class Article {
publishedAt: DateTime.now(),
source: Source(id: 'default', name: 'Default')
);

bool isValid() {
return (url != null && title != null && author != null && content != null && urlToImage != null &&
description != null && publishedAt != null && source.isValid());
}

@override
String toString() {
return '''
Article {
url: $url,
title: $title,
author: $author,
content: $content,
urlToImage: $urlToImage,
source: $source,
description: $description,
publishedAt: $publishedAt,
}
''';
}
}

class Source {
Expand All @@ -52,4 +73,8 @@ class Source {
id = j['id'];
name = j['name'];
}

bool isValid() {
return (id != null && name != null);
}
}
33 changes: 31 additions & 2 deletions lib/data/services/local_storage_service.dart
@@ -1,13 +1,42 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'package:rse/data/models/all.dart';
import 'package:rse/presentation/utils/all.dart';

class LocalStorageService {
Future<void> saveData(String key, String value) async {
Future<void> saveData(String key, String value, {bool overwrite = true}) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(key, value);
if (overwrite || !prefs.containsKey(key)) {
await prefs.setString(key, value);
}
}

Future<String?> loadData(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(key);
}

Future<List<Article>> getCachedArticles() async {
var data = await loadData('articles');

if (data != null && data.isNotEmpty) {
// if (false && data != null && data.isNotEmpty) {
return _mapArticlesFromData(jsonDecode(data)['results']);
} else {
final d = await loadJsonFile('assets/news.json');
if (d != null && d.isNotEmpty) {
return _mapArticlesFromData(d['results']);
}
}
debugPrint('Error: Error loading cached articles');
return [];
}

List<Article> _mapArticlesFromData(dynamic data) {
return (data as List<dynamic>)
.map((item) => Article.fromJson(item as Map<String, dynamic>))
.toList();
}
}
24 changes: 15 additions & 9 deletions lib/data/services/news_service.dart
@@ -1,26 +1,32 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/foundation.dart';

import 'package:rse/data/models/all.dart';
import 'package:rse/presentation/utils/helpers.dart';
import 'package:rse/presentation/utils/constants.dart';
import 'package:rse/data/services/all.dart';
import 'package:rse/presentation/utils/all.dart';

class NewsService {
final LocalStorageService _localStorage = LocalStorageService();

Future<List<Article>> fetchArticles() async {
try {
final response = await http.get(Uri.parse("$newsApi"));
if (kDebugMode) throw Error();
final response = await http.get(Uri.parse(newsApi));
if (response.statusCode == 200) {
final Map<String, dynamic> data = json.decode(response.body);
_localStorage.saveData('articles', response.body);
final List<dynamic> articles = data['results'] as List<dynamic>;
List<Article> articleItems = articles.map((item) => Article.fromJson(item)).toList();
return articleItems;
debugPrint("Articles from API");
return articles.map((item) => Article.fromJson(item)).toList();
} else {
printResponse(response);
throw Error();
}
} catch (e) {
print('Error fetching articles: $e');
if (kDebugMode) {
debugPrint("Error: Fetching articles, loading from cache.");
}
return await _localStorage.getCachedArticles();
}
return [];
}

}
6 changes: 3 additions & 3 deletions lib/main.dart
Expand Up @@ -13,12 +13,12 @@ void main() {
runApp(
MultiBlocProvider(
providers: [
BlocProvider<PortfolioCubit>(
create: (_) => PortfolioCubit(),
),
BlocProvider<NewsCubit>(
create: (_) => NewsCubit(),
),
BlocProvider<PortfolioCubit>(
create: (_) => PortfolioCubit(),
),
],
child: const MyApp(),
),
Expand Down
7 changes: 3 additions & 4 deletions lib/presentation/screens/home_screen.dart
Expand Up @@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:rse/data/cubits/all.dart';
import 'package:rse/data/models/all.dart';
import 'package:rse/data/models/all.dart' as models;
import 'package:rse/presentation/widgets/all.dart';

class HomeScreen extends StatefulWidget {
Expand Down Expand Up @@ -68,8 +68,7 @@ class _HomeScreenState extends State<HomeScreen> {
return true;
},
),
OptionsTable(),
BlocBuilder<NewsCubit, List<Article>>(
BlocBuilder<NewsCubit, List<models.Article>>(
builder: (context, articles) {
if (articles.isEmpty) {
return const CircularProgressIndicator();
Expand All @@ -78,7 +77,7 @@ class _HomeScreenState extends State<HomeScreen> {
shrinkWrap: true,
itemCount: articles.length,
itemBuilder: (context, index) {
return ArticleWidget(article: articles[index]);
return Article(article: articles[index]);
},
);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/presentation/utils/constants.dart
Expand Up @@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:rse/presentation/screens/all.dart';

const api = "https://192.168.0.22:7254";
const newsApi = "https://newsdata.io/api/1/news?apikey=1&q=business";
const newsApi = "https://newsdata.io/api/1/news?apikey=1&category=business&language=en";

const List<Widget> tabs = [
HomeScreen(title: 'Home'),
Expand Down
24 changes: 17 additions & 7 deletions lib/presentation/utils/helpers.dart
@@ -1,11 +1,27 @@
import 'dart:convert';
import 'package:intl/intl.dart';
import 'package:http/http.dart' as http;
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart' show rootBundle;

Future<dynamic> loadJsonFile(String path) async {
try {
String content = await rootBundle.loadString(path);
return json.decode(content);
} catch (e) {
if (kDebugMode) {
debugPrint('Error loading JSON file: $e');
}
}
return null;
}

void printResponse(http.Response response) {
final responseMap = json.decode(response.body) as Map<String, dynamic>;
responseMap.forEach((key, value) {
print('$key: $value');
if (kDebugMode) {
debugPrint('$key: $value');
}
});
}

Expand Down Expand Up @@ -34,15 +50,9 @@ String shortenMoney(String value) {
}

String formatUtcToDM(DateTime utcTime) {
// Convert the UTC time to the local time zone
final localTime = utcTime.toLocal();

// Extract the day and month components
final day = localTime.day;
final month = localTime.month;

// Format the day and month as 'd/m'
final formattedDate = '$day/$month';

return formattedDate;
}
110 changes: 79 additions & 31 deletions lib/presentation/widgets/article.dart
@@ -1,44 +1,92 @@
import 'package:flutter/material.dart';
import 'package:rse/data/models/all.dart' as models;

import 'package:rse/data/models/all.dart';

class ArticleWidget extends StatelessWidget {
final Article article;
class Article extends StatelessWidget {
final models.Article article;

const ArticleWidget({required this.article});
const Article({Key? key, required this.article}) : super(key: key);

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (article.urlToImage != null)
Image.network(
article.urlToImage!,
fit: BoxFit.cover,
height: 200,
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 30),
child: Flexible(
flex: 2,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
article.title ?? '',
style: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
article.author ?? '',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
article.source.id ?? '',
style: const TextStyle(
fontSize: 18,
color: Colors.red,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
article.description ?? '',
style: TextStyle(fontSize: 14),
),
const SizedBox(height: 4),
Text(
'Published on: ${article.publishedAt?.toString() ?? ''}',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
const SizedBox(height: 4),
Text(
article.url?.toString() ?? '',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
const SizedBox(height: 8),
],
),
),
const SizedBox(height: 8),
Text(
article.title ?? '',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
const SizedBox(width: 50),
Flexible(
flex: 1,
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Container(
width: MediaQuery.of(context).size.width / 3,
height: 200,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
article.urlToImage ??
"https://awlights.com/wp-content/uploads/sites/31/2017/05/placeholder-news.jpg",
),
fit: BoxFit.cover,
alignment: Alignment.center,
),
),
),
),
),
),
const SizedBox(height: 4),
Text(
article.description ?? '',
style: TextStyle(fontSize: 14),
),
const SizedBox(height: 4),
Text(
'Published on: ${article.publishedAt?.toString() ?? ''}',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
],
],
),
),
);
}
Expand Down
7 changes: 5 additions & 2 deletions pubspec.yaml
@@ -1,6 +1,6 @@
name: rse
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.0.1 <4.0.0'
Expand All @@ -14,11 +14,14 @@ dependencies:
syncfusion_flutter_charts: ^21.2.9
flutter_bloc: ^8.0.0
equatable: ^2.0.5
shared_preferences: ^2.1.2
shared_preferences: ^2.0.9
intl: ^0.18.0
flutter_chart: ^0.1.6
bloc: ^8.1.2

assets:
- assets/news.json


dev_dependencies:
flutter_lints: ^2.0.0
Expand Down

0 comments on commit 2256ec7

Please sign in to comment.