Reputation: 175
I am trying to update the list of the models when the scroll view reaches the end, while I was able to do so, I am unable to do it smoothly. I want to load the new images from the API into the List of models whenever the user reaches the middle of the scroll so that the scrolling feels smooth as in the current scenario, there is a second or two of pause when the user hits the end after which the new elements will be added so the user won't know if the new elements are added. Also, my current approach is putting too much load on the main thread and is crashing the app.
here's the code below:
home.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:wallpaper_app/data/data.dart';
import 'package:wallpaper_app/model/categories.dart';
import 'package:wallpaper_app/model/wallpaper_model.dart';
import 'package:wallpaper_app/network/network.dart';
import 'package:wallpaper_app/widgets/branding.dart';
import 'package:wallpaper_app/widgets/list_tile.dart';
import 'package:wallpaper_app/widgets/wallpaper_tile.dart';
var pageIndex=1;
Data data = Data();
List<CategoriesModel> categories;
List<WallpaperModel> wallpapers =[];
class Home extends StatefulWidget {
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
@override
void initState() {
// TODO: implement initState
super.initState();
_controller = ScrollController();
_controller.addListener(() {
if (_controller.position.atEdge) {
if (_controller.position.pixels == 0)
print('top');
// you are at top position
else{
print('end');
incrementIndex();
getWallpapers(pageIndex);
}
// you are at bottom position
}
});
categories = data.getCategories();
getWallpapers(pageIndex);
}
ScrollController _controller;
void incrementIndex(){
setState(() {
pageIndex++;
});
}
void getWallpapers(var index) async {
try {
Network network = Network();
Map<String, dynamic> jsonData = await network.getCuratedWallpapers(
index, 80);
jsonData["photos"].forEach((element) {
WallpaperModel wallpaperModel = WallpaperModel.fromMap(element);
setState(() {
wallpapers.add(wallpaperModel);
});
// for (var wallpaper in wallpapers){
// print(wallpaper.src.small);
// }
});
}
catch(e){
print(e);
}
}
@override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
systemNavigationBarColor: Color(0xff999999), // navigation bar color
));
return Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
title: Brand(),
),
body: SingleChildScrollView(
controller: _controller,
child: Container(
child: Column(
children: [
Container(
height: 100,
child: ListView.builder(
// controller: _controller,
padding: EdgeInsets.all(10),
itemCount: categories.length,
// shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return CategoriesTile(
imageUrl: categories[index].imageUrl,
title: categories[index].categoriesName,
);
}),
),
SizedBox(
height: 20,
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10),
child: GridView.builder(
physics: ScrollPhysics(),
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 6.0,
mainAxisSpacing: 6.0,
childAspectRatio: 0.6),
itemBuilder: (context,index){
// if(index==50){
//// incrementIndex();
// getWallpapers(pageIndex+1);
// }
return WallpaperTile(small: wallpapers[index].src.portrait,);
},
itemCount: wallpapers.length,
),
)
],
),
),
),
);
}
}
wallpaper_model.dart
class WallpaperModel {
String photographer;
String photographerUrl;
int photographerId;
SrcModel src;
WallpaperModel(
{this.photographer, this.photographerUrl, this.photographerId, this.src});
factory WallpaperModel.fromMap(Map<String,dynamic> jsonData){
return WallpaperModel(
photographer: jsonData["photographer"],
photographerId: jsonData["photographer_id"],
photographerUrl: jsonData["photographer_url"],
src: SrcModel.fromMap(jsonData["src"])
);
}
}
class SrcModel {
String original;
String small;
String portrait;
SrcModel({this.original, this.small, this.portrait});
factory SrcModel.fromMap(Map<String, dynamic> jsonData) {
return SrcModel(
original: jsonData["original"],
small: jsonData["small"],
portrait: jsonData["portrait"]);
}
}
wallpaper_tile.dart
import 'package:flutter/material.dart';
class WallpaperTile extends StatelessWidget {
final String small;
WallpaperTile({this.small});
@override
Widget build(BuildContext context) {
return Container(
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child:
Image.network(
small,
fit: BoxFit.cover,
)),
);
}
}
network.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
const apiKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
class Network{
Future<dynamic> getCuratedWallpapers(var pageNo, var maxWallpapers) async{
var response = await http.get("https://api.pexels.com/v1/curated/?page=$pageNo&per_page=$maxWallpapers",
headers: {
"Authorization":apiKey
});
return jsonDecode(response.body);
}
}
the top list view is just for categories and can be ignored. it's the grid view.builder that has the core functionality
Upvotes: 3
Views: 3129
Reputation: 822
try SliverGrid instead of GridView.builder in your home.dart
.
because when you will using GridView.builder
it will load it's all possible result at once instead of this when you use SliverGrid
it will load only those data which is currently present in screen
.
SliverGrid(delegate: SliverChildBuilderDelegate((context, int index) {
final _wallpaper = wallpaper.photos ? [index];
return Container(decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0),), height: MediaQuery
.of(context)
.size
.height / 2, child: GestureDetector(onTap: () {
//Wallpaper Preview
},
child: Hero(tag: '${_wallpaper?.id}',
child: ClipRRect(borderRadius: BorderRadius.circular(10.0),
child: CachedNetworkImage(fit: BoxFit.cover,
imageUrl: _wallpaper!.src!.large!,
filterQuality: FilterQuality.high,
progressIndicatorBuilder: (context, url, progress) => Center(child: CircularProgressIndicator()),),),),),);
}, childCount: wallpaper.photos?.length),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 0.5, crossAxisSpacing: 5.0 mainAxisSpacing: 5.0,),),
note: Remove extra that you don't required.
Upvotes: 0
Reputation: 175
While I couldn't solve the main issue(loading every time the scroll reached the mid of the screen so that the user doesn't have to wait and see the progress bar) but I was able to solve all the other issues besides that.
main.dart
import 'package:flutter/material.dart';
import 'package:wallpaper_app/data/provider_data.dart';
import 'package:wallpaper_app/screens/home.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context)=>ProviderData(),
child: MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: Colors.black,
),
title: 'Wallpaper App',
home: Home()
),
);
}
}
home.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:wallpaper_app/data/data.dart';
import 'package:wallpaper_app/data/provider_data.dart';
import 'package:wallpaper_app/model/categories.dart';
import 'package:wallpaper_app/model/wallpaper_model.dart';
import 'package:wallpaper_app/network/network.dart';
import 'package:wallpaper_app/widgets/branding.dart';
import 'package:wallpaper_app/widgets/list_tile.dart';
import 'package:wallpaper_app/widgets/wallpaper_tile.dart';
import 'package:provider/provider.dart';
import 'package:flutter/services.dart';
import 'package:flutter_pagewise/flutter_pagewise.dart';
Data data = Data();
List<CategoriesModel> categories;
class Home extends StatefulWidget {
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
@override
void initState() {
// TODO: implement initState
super.initState();
categories = data.getCategories();
WidgetsBinding.instance.addPostFrameCallback((_) async {
_controller = ScrollController();
_controller.addListener(() {
if (_controller.position.atEdge) {
if (_controller.position.pixels == 0)
print('top');
// you are at top position
else{
print('end');
// incrementIndex();
if(Provider.of<ProviderData>(context,listen: false).pageIndex>5){
print('image cache: ${imageCache.currentSize}');
imageCache.clear();
}
Provider.of<ProviderData>(context,listen: false).incrementPage();
getWallpapers(Provider.of<ProviderData>(context,listen: false).pageIndex);
}
// you are at bottom position
}
});
getWallpapers(Provider.of<ProviderData>(context,listen: false).pageIndex);
});
}
ScrollController _controller;
void getWallpapers(var index) async {
if(!Provider.of<ProviderData>(context,listen: false).isPerformingRequest){
Provider.of<ProviderData>(context,listen: false).togglePerformingRequest();
try {
Network network = Network();
Map<String, dynamic> jsonData = await network.getCuratedWallpapers(
index, 80);
jsonData["photos"].forEach((element) {
WallpaperModel wallpaperModel = WallpaperModel.fromMap(element);
setState(() {
Provider.of<ProviderData>(context,listen: false).addWallpapers(wallpaperModel);
});
});
}
catch(e){
print(e);
}
Provider.of<ProviderData>(context,listen: false).togglePerformingRequest();
}
}
@override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays([]);
return Scaffold(
// appBar: AppBar(
// backgroundColor: Colors.black,
// elevation: 0,
//// backgroundColor: Colors.white,
// title: Brand(),
// ),
body:
// SingleChildScrollView(
// controller: _controller,
// child:
Container(
child: Column(
children: [
// Container(
// height: 100,
// child: ListView.builder(
//// controller: _controller,
// padding: EdgeInsets.all(10),
// itemCount: categories.length,
// shrinkWrap: true,
// scrollDirection: Axis.horizontal,
// itemBuilder: (context, index) {
// return CategoriesTile(
// imageUrl: categories[index].imageUrl,
// title: categories[index].categoriesName,
// );
// }),
// ),
// SizedBox(
// height: 20,
// ),
Expanded(
// padding: EdgeInsets.symmetric(horizontal: 10),
child: GridView.builder(
controller: _controller,
physics: ScrollPhysics(),
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 6.0,
mainAxisSpacing: 6.0,
childAspectRatio: 0.6),
addAutomaticKeepAlives: false,
itemBuilder: (context,index){
if(index==(Provider.of<ProviderData>(context,listen: false).wallpapers!=null?Provider.of<ProviderData>(context,listen: false).wallpapers.length+0:80)){
return _buildProgressIndicator();
}
else
return WallpaperTile(small: Provider.of<ProviderData>(context,listen: false).wallpapers[index].src.medium,);
},
itemCount: Provider.of<ProviderData>(context,listen: false).wallpapers!=null?Provider.of<ProviderData>(context,listen: false).wallpapers.length+1:81,
),
)
],
),
),
// ),
);
}
}
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: 1,
child: new CircularProgressIndicator(),
),
),
);
}
wallpaper_model.dart
class WallpaperModel {
String photographer;
String photographerUrl;
int photographerId;
SrcModel src;
WallpaperModel(
{this.photographer, this.photographerUrl, this.photographerId, this.src});
factory WallpaperModel.fromMap(Map<String,dynamic> jsonData){
return WallpaperModel(
photographer: jsonData["photographer"],
photographerId: jsonData["photographer_id"],
photographerUrl: jsonData["photographer_url"],
src: SrcModel.fromMap(jsonData["src"])
);
}
}
class SrcModel {
String original;
String small;
String portrait;
String medium;
SrcModel({this.original, this.small, this.portrait,this.medium});
factory SrcModel.fromMap(Map<String, dynamic> jsonData) {
return SrcModel(
original: jsonData["original"],
small: jsonData["small"],
medium: jsonData["medium"],
portrait: jsonData["portrait"]);
}
}
network.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
const apiKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
class Network{
Future<dynamic> getCuratedWallpapers(var pageNo, var maxWallpapers) async{
var response = await http.get("https://api.pexels.com/v1/curated/?page=$pageNo&per_page=$maxWallpapers",
headers: {
"Authorization":apiKey,
});
print(response.statusCode);
// print(response.body);
return jsonDecode(response.body);
}
}
wallpaper_tile.dart
import 'package:flutter/material.dart';
class WallpaperTile extends StatelessWidget {
final String small;
WallpaperTile({this.small});
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(5),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
color: Colors.grey
),
child: ClipRRect(
borderRadius: BorderRadius.circular(25),
child:
Image.network(
small,
fit: BoxFit.cover,
cacheHeight: 400,
cacheWidth: 225,
)
),
);
}
}
provider_data.dart
import 'package:flutter/foundation.dart';
import 'package:wallpaper_app/model/wallpaper_model.dart';
class ProviderData extends ChangeNotifier{
List<WallpaperModel> wallpapers =[];
bool isPerformingRequest = false;
var pageIndex=1;
void togglePerformingRequest(){
isPerformingRequest = !isPerformingRequest;
notifyListeners();
}
void incrementPage(){
pageIndex++;
print(pageIndex);
notifyListeners();
}
void addWallpapers(WallpaperModel wallpaperModel){
wallpapers.add(wallpaperModel);
notifyListeners();
}
}
Upvotes: 5
Reputation: 7640
You need to use NotificationListener
with ScrollNotification
. Modify this with your requirements.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int present = 0;
int perPage = 15;
final originalItems = List<String>.generate(10000, (i) => "Item $i");
var items = List<String>();
@override
void initState() {
super.initState();
setState(() {
items.addAll(originalItems.getRange(present, present + perPage));
present = present + perPage;
});
}
void loadMore() {
setState(() {
if((present + perPage )> originalItems.length) {
items.addAll(
originalItems.getRange(present, originalItems.length));
} else {
items.addAll(
originalItems.getRange(present, present + perPage));
}
present = present + perPage;
});
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels ==
scrollInfo.metrics.maxScrollExtent / 2) {
loadMore();
}
},
child: ListView.builder(
itemCount: (present <= originalItems.length) ? items.length + 1 : items.length,
itemBuilder: (context, index) {
return (index == items.length ) ?
Container(
color: Colors.greenAccent,
child: FlatButton(
child: Text("Load More"),
onPressed: () {
loadMore();
},
),
)
:
ListTile(
title: Text('${items[index]}'),
);
},
),
),
);
}
}
Read more.
Upvotes: 1