flutter create flutter_infinite_list
name: flutter_infinite_list description: A new Flutter project. version: 1.0.0+1 environment: sdk: ">=2.0.0-dev.68.0 <3.0.0" dependencies: flutter: sdk: flutter flutter_bloc: 0.4.11 http: 0.12.0 equatable: 0.1.1 dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true
flutter packages get
[ { "userId": 1, "id": 1, "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" }, { "userId": 1, "id": 2, "title": "qui est esse", "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla" } ]
import 'package:equatable/equatable.dart'; class Post extends Equatable { final int id; final String title; final String body; Post({this.id, this.title, this.body}) : super([id, title, body]); @override String toString() => 'Post { id: $id }'; }
import 'package:equatable/equatable.dart'; abstract class PostEvent extends Equatable {} class Fetch extends PostEvent { @override String toString() => 'Fetch'; }
import 'package:equatable/equatable.dart'; import 'package:flutter_infinite_list/models/models.dart'; abstract class PostState extends Equatable { PostState([Iterable props]) : super(props); } class PostUninitialized extends PostState { @override String toString() => 'PostUninitialized'; } class PostInitialized extends PostState { final List<Post> posts; final bool hasError; final bool hasReachedMax; PostInitialized({ this.hasError, this.posts, this.hasReachedMax, }) : super([posts, hasError, hasReachedMax]); factory PostInitialized.success(List<Post> posts) { return PostInitialized( posts: posts, hasError: false, hasReachedMax: false, ); } factory PostInitialized.failure() { return PostInitialized( posts: [], hasError: true, hasReachedMax: false, ); } PostInitialized copyWith({ List<Post> posts, bool hasError, bool hasReachedMax, }) { return PostInitialized( posts: posts ?? this.posts, hasError: hasError ?? this.hasError, hasReachedMax: hasReachedMax ?? this.hasReachedMax, ); } @override String toString() => 'PostInitialized { posts: ${posts.length}, hasError: $hasError, hasReachedMax: $hasReachedMax }'; }
import 'package:bloc/bloc.dart'; import 'package:meta/meta.dart'; import 'package:http/http.dart' as http; import 'package:flutter_infinite_list/bloc/bloc.dart'; import 'package:flutter_infinite_list/models/models.dart'; class PostBloc extends Bloc<PostEvent, PostState> { final http.Client httpClient; PostBloc({@required this.httpClient}); @override // TODO: implement initialState PostState get initialState => null; @override Stream<PostState> mapEventToState( PostState currentState, PostEvent event, ) async* { // TODO: implement mapEventToState yield null; } }
import 'package:bloc/bloc.dart'; import 'package:meta/meta.dart'; import 'package:http/http.dart' as http; import 'package:flutter_infinite_list/bloc/bloc.dart'; import 'package:flutter_infinite_list/models/models.dart'; class PostBloc extends Bloc<PostEvent, PostState> { final http.Client httpClient; PostBloc({@required this.httpClient}); @override PostState get initialState => PostState.initial(); @override Stream<PostState> mapEventToState( PostState currentState, PostEvent event, ) async* { // TODO: implement mapEventToState yield null; } }
import 'dart:convert'; import 'package:meta/meta.dart'; import 'package:http/http.dart' as http; import 'package:bloc/bloc.dart'; import 'package:flutter_infinite_list/bloc/bloc.dart'; import 'package:flutter_infinite_list/models/models.dart'; class PostBloc extends Bloc<PostEvent, PostState> { final http.Client httpClient; PostBloc({@required this.httpClient}); @override get initialState => PostState.initial(); @override Stream<PostState> mapEventToState(currentState, event) async* { if (event is Fetch && !currentState.hasReachedMax) { try { final posts = await _fetchPosts(currentState.posts.length, 20); if (posts.isEmpty) { yield currentState.copyWith(hasReachedMax: true); } else { yield PostState.success(currentState.posts + posts); } } catch (_) { yield PostState.failure(); } } } Future<List<Post>> _fetchPosts(int startIndex, int limit) async { final response = await httpClient.get( 'https://jsonplaceholder.typicode.com/posts?_start=$startIndex&_limit=$limit'); if (response.statusCode == 200) { final data = json.decode(response.body) as List; return data.map((rawPost) { return Post( id: rawPost['id'], title: rawPost['title'], body: rawPost['body'], ); }).toList(); } else { throw Exception('error fetching posts'); } } }
import 'dart:convert'; import 'package:meta/meta.dart'; import 'package:rxdart/rxdart.dart'; import 'package:http/http.dart' as http; import 'package:bloc/bloc.dart'; import 'package:flutter_infinite_list/bloc/bloc.dart'; import 'package:flutter_infinite_list/models/models.dart'; class PostBloc extends Bloc<PostEvent, PostState> { final http.Client httpClient; PostBloc({@required this.httpClient}); @override Stream<PostEvent> transform(Stream<PostEvent> events) { return (events as Observable<PostEvent>) .debounce(Duration(milliseconds: 500)); } @override get initialState => PostState.initial(); @override Stream<PostState> mapEventToState(currentState, event) async* { if (event is Fetch && !currentState.hasReachedMax) { try { final posts = await _fetchPosts(currentState.posts.length, 20); if (posts.isEmpty) { yield currentState.copyWith(hasReachedMax: true); } else { yield PostState.success(currentState.posts + posts); } } catch (_) { yield PostState.failure(); } } } Future<List<Post>> _fetchPosts(int startIndex, int limit) async { final response = await httpClient.get( 'https://jsonplaceholder.typicode.com/posts?_start=$startIndex&_limit=$limit'); if (response.statusCode == 200) { final data = json.decode(response.body) as List; return data.map((rawPost) { return Post( id: rawPost['id'], title: rawPost['title'], body: rawPost['body'], ); }).toList(); } else { throw Exception('error fetching posts'); } } }
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Infinite Scroll', home: Scaffold( appBar: AppBar( title: Text('Posts'), ), body: HomePage(), ), ); } }
class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { final _scrollController = ScrollController(); final PostBloc _postBloc = PostBloc(httpClient: http.Client()); final _scrollThreshold = 200.0; _HomePageState() { _scrollController.addListener(_onScroll); _postBloc.dispatch(Fetch()); } @override Widget build(BuildContext context) { return BlocBuilder( bloc: _postBloc, builder: (BuildContext context, PostState state) { if (state.isInitializing) { return Center( child: CircularProgressIndicator(), ); } if (state.isError) { return Center( child: Text('failed to fetch posts'), ); } if (state.posts.isEmpty) { return Center( child: Text('no posts'), ); } return ListView.builder( itemBuilder: (BuildContext context, int index) { return index >= state.posts.length ? BottomLoader() : PostWidget(post: state.posts[index]); }, itemCount: state.hasReachedMax ? state.posts.length : state.posts.length + 1, controller: _scrollController, ); }, ); } @override void dispose() { _postBloc.dispose(); super.dispose(); } void _onScroll() { final maxScroll = _scrollController.position.maxScrollExtent; final currentScroll = _scrollController.position.pixels; if (maxScroll - currentScroll <= _scrollThreshold) { _postBloc.dispatch(Fetch()); } } }
class BottomLoader extends StatelessWidget { @override Widget build(BuildContext context) { return Container( alignment: Alignment.center, child: Center( child: SizedBox( width: 33, height: 33, child: CircularProgressIndicator( strokeWidth: 1.5, ), ), ), ); } }
class PostWidget extends StatelessWidget { final Post post; const PostWidget({Key key, @required this.post}) : super(key: key); @override Widget build(BuildContext context) { return ListTile( leading: Text( post.id.toString(), style: TextStyle(fontSize: 10.0), ), title: Text('${post.title}'), isThreeLine: true, subtitle: Text(post.body), dense: true, ); } }
Source: https://habr.com/ru/post/435688/
All Articles