Skip to content

Commit 3f853c2

Browse files
committed
Request more articles from server beyond page 1
1 parent 31bda5f commit 3f853c2

File tree

5 files changed

+97
-28
lines changed

5 files changed

+97
-28
lines changed

lib/main.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ class MyApp extends StatelessWidget {
1717
Widget build(BuildContext context) {
1818
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
1919

20-
ArticleBloc bloc = ArticleBloc(api: Api());
20+
Api api = new Api();
21+
ArticleBloc bloc = ArticleBloc(api: api);
22+
2123
bloc.getArticles();
2224

2325
return ArticleBlocProvider(
@@ -45,6 +47,7 @@ class HomePage extends StatelessWidget {
4547
itemStream: ArticleBlocProvider.of(context).articles,
4648
itemBuilder: (context, article, onBackFlip, height) =>
4749
ArticlePage(article, onBackFlip, height),
50+
getItemsCallback: ArticleBlocProvider.of(context).getArticles,
4851
height: height,
4952
),
5053
),

lib/service/api.dart

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,24 @@
11
import 'dart:async';
2-
import 'dart:convert';
32

43
import 'package:flutter_board/keys.dart';
5-
import 'package:flutter_board/model/article.dart';
64
import 'package:http/http.dart' as http;
5+
import 'package:meta/meta.dart';
76

87
class Api {
98
final String _baseUrl = "https://newsapi.org/v2/top-headlines?sources=";
109

11-
Future<List<Article>> getArticles() async {
12-
String url = Uri.encodeFull(_baseUrl + 'cnn,bbc-news');
10+
Future<String> getArticles({
11+
@required int page,
12+
@required int pageSize,
13+
}) async {
14+
String url = Uri.encodeFull(_baseUrl + 'cnn,bbc-news&pageSize=$pageSize&page=$page');
1315
try {
1416
http.Response response = await http.get(url, headers: _headers());
15-
1617
if (response.statusCode == 200) {
17-
var localData = json.decode(response.body);
18-
if (localData != null && localData["articles"] != null) {
19-
List<Article> articles = (localData["articles"] as List<dynamic>)
20-
.map((article) => Article(
21-
article['source']['name'],
22-
article['author'],
23-
article['title'],
24-
article['description'],
25-
article['url'],
26-
article['urlToImage'],
27-
))
28-
.toList();
29-
return articles;
30-
}
18+
return response.body;
3119
}
3220
} on Exception {}
33-
return [];
21+
return null;
3422
}
3523

3624
Map<String, String> _headers() {

lib/service/article_bloc.dart

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:async';
2+
import 'dart:convert';
23

34
import 'package:flutter_board/model/article.dart';
45
import 'package:flutter_board/service/api.dart';
@@ -9,13 +10,33 @@ import 'package:rxdart/rxdart.dart';
910
class ArticleBloc {
1011
final Api api;
1112

13+
static const int _pageSize = 10;
14+
int _nextPage = 1;
15+
16+
// Assume there is at least one article in the server
17+
int _totalItemsForRequestedSources = 1;
18+
1219
final _articlesController = PublishSubject<List<Article>>();
1320

14-
ArticleBloc({@required this.api}) {}
21+
ArticleBloc({@required this.api});
1522

1623
// Inputs
17-
void getArticles() async {
18-
_articlesController.add(await api.getArticles());
24+
Future<void> getArticles({bool refresh = false}) async {
25+
List<Article> articles;
26+
if (refresh) {
27+
articles = await _getArticles();
28+
// Send a null list prior to the real list to allow the flip panel to reset
29+
_articlesController.add(null);
30+
_articlesController.add(articles);
31+
_nextPage++;
32+
} else {
33+
if (_totalItemsForRequestedSources > (_nextPage - 1) * _pageSize) {
34+
articles = await _getArticles(page: _nextPage);
35+
_articlesController.add(articles);
36+
_nextPage++;
37+
} else
38+
print("No more items");
39+
}
1940
}
2041

2142
// Outputs
@@ -24,4 +45,29 @@ class ArticleBloc {
2445
void close() {
2546
_articlesController.close();
2647
}
48+
49+
Future<List<Article>> _getArticles(
50+
{int page = 1, int pageSize = _pageSize}) async {
51+
String jsonString = await api.getArticles(page: page, pageSize: pageSize);
52+
if (jsonString != null) {
53+
var data = json.decode(jsonString);
54+
if (data != null &&
55+
data["totalResults"] != null &&
56+
data["articles"] != null) {
57+
_totalItemsForRequestedSources = data["totalResults"];
58+
List<Article> articles = (data["articles"] as List<dynamic>)
59+
.map((article) => Article(
60+
article['source']['name'],
61+
article['author'],
62+
article['title'],
63+
article['description'],
64+
article['url'],
65+
article['urlToImage'],
66+
))
67+
.toList();
68+
return articles;
69+
}
70+
}
71+
return null;
72+
}
2773
}

lib/ui/article_page.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ class ArticlePage extends StatelessWidget {
108108
padding: EdgeInsets.all(10.0),
109109
child: new LayoutBuilder(
110110
builder: (BuildContext context, BoxConstraints constraints) {
111-
print(constraints);
112111
return new Text(
113112
article.description,
114113
overflow: TextOverflow.ellipsis,

lib/ui/my_flip_panel.dart

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import 'dart:math' as math;
55

66
typedef Widget ItemBuilder<T>(BuildContext, T, VoidCallback, double);
77

8+
// Callback that allows to request for more items to the server or to refresh
9+
// the list if refresh is true.
10+
typedef void GetItems({bool refresh});
11+
812
enum FlipDirection { up, down, none }
913

1014
enum LastFlip { none, previous, next }
@@ -16,15 +20,19 @@ class FlipPanel<T> extends StatefulWidget {
1620
final Duration duration;
1721
final double height;
1822
final Stream<List<T>> itemStream;
23+
final GetItems getItemsCallback;
1924

2025
FlipPanel({
2126
Key key,
2227
@required this.itemBuilder,
2328
@required this.itemStream,
24-
this.height,
29+
@required this.getItemsCallback,
30+
@required this.height,
2531
this.duration = const Duration(milliseconds: 100),
2632
}) : assert(itemBuilder != null),
2733
assert(itemStream != null),
34+
assert(getItemsCallback != null),
35+
assert(height != null),
2836
super(key: key);
2937

3038
@override
@@ -55,6 +63,10 @@ class _FlipPanelState<T> extends State<FlipPanel>
5563
// depending on the user flipping.
5664
int _availableItems = 0;
5765

66+
// Number of items between the _currentIndex and _availableItemns below which
67+
// we launch a request to server for more items (next page).
68+
int _updateThreshold = 5;
69+
5870
Widget _prevChild, _currentChild, _nextChild;
5971
Widget _upperChild1, _upperChild2;
6072
Widget _lowerChild1, _lowerChild2;
@@ -70,6 +82,8 @@ class _FlipPanelState<T> extends State<FlipPanel>
7082

7183
LastFlip _lastFlip = LastFlip.none;
7284

85+
bool _shouldShowNoMoreItemsMessage = false;
86+
7387
@override
7488
void initState() {
7589
super.initState();
@@ -97,6 +111,11 @@ class _FlipPanelState<T> extends State<FlipPanel>
97111
: _lastFlip == LastFlip.previous && _currentIndex > 0
98112
? _currentIndex - 1
99113
: _currentIndex;
114+
// Avoid going beyond max. items of widgets list
115+
if (_lastFlip == LastFlip.next &&
116+
_currentIndex == _availableItems - _updateThreshold) {
117+
widget.getItemsCallback();
118+
}
100119
}
101120
})
102121
..addListener(() {
@@ -245,10 +264,10 @@ class _FlipPanelState<T> extends State<FlipPanel>
245264
if (_direction == FlipDirection.down && _currentIndex == 0) {
246265
_dragExtent = 0.0;
247266
}
248-
// Avoid going beyond max. items of widgets list
249267
if (_direction == FlipDirection.up &&
250268
_currentIndex == widgets.length - 1) {
251269
_dragExtent = 0.0;
270+
_shouldShowNoMoreItemsMessage = true;
252271
}
253272
if (_dragExtent.abs() < _flipExtent) {
254273
_controller.value = (_dragExtent / _flipExtent).abs();
@@ -263,7 +282,13 @@ class _FlipPanelState<T> extends State<FlipPanel>
263282
void _handleDragEnd(DragEndDetails details) {
264283
_dragging = false;
265284

266-
if (_dragExtent == 0.0) return;
285+
if (_dragExtent == 0.0) {
286+
if (_shouldShowNoMoreItemsMessage) {
287+
_showNoMoreItemsMessage();
288+
_shouldShowNoMoreItemsMessage = false;
289+
}
290+
return;
291+
}
267292

268293
final double velocity = details.primaryVelocity;
269294
final bool fast = velocity.abs() > _kFastThreshold;
@@ -437,4 +462,12 @@ class _FlipPanelState<T> extends State<FlipPanel>
437462
child: content,
438463
);
439464
}
465+
466+
void _showNoMoreItemsMessage() {
467+
Scaffold.of(context).showSnackBar(
468+
SnackBar(
469+
content: Text("No more articles for selected sources"),
470+
),
471+
);
472+
}
440473
}

0 commit comments

Comments
 (0)