How can I refresh listview.builder in flutter? - flutter

how can I add refresh to listview.builder in flutter?
This is my all code from posts screen when I delete post or add new post not realtime just after hot restart it worked I want to add refresh to listview I called getPosts method in refreshIndecator but not worked please any one can help me? Thanks.
Code :
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:data_connection_checker/data_connection_checker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_icons/flutter_icons.dart';
import 'package:social_media_app/models/post.dart';
import 'package:social_media_app/posts/create_post.dart';
import 'package:social_media_app/utils/firebase.dart';
import 'package:social_media_app/widgets/indicators.dart';
import 'package:social_media_app/widgets/posts_view.dart';
class Timeline extends StatefulWidget {
#override
_TimelineState createState() => _TimelineState();
}
class _TimelineState extends State<Timeline> {
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
List<DocumentSnapshot> post = [];
bool isLoading = false;
DocumentSnapshot lastDocument;
ScrollController _scrollController;
getPosts() async {
if (isLoading) {
return CircularProgressIndicator();
}
if (mounted)
setState(() {
isLoading = true;
});
QuerySnapshot querySnapshot;
if (lastDocument == null) {
querySnapshot =
await postRef.orderBy('timestamp', descending: true).get();
} else {
querySnapshot = await postRef
.orderBy('timestamp', descending: true)
.startAfterDocument(lastDocument)
.get();
}
lastDocument = querySnapshot.docs[querySnapshot.docs.length - 1];
post.addAll(querySnapshot.docs);
if (mounted)
setState(() {
isLoading = false;
});
}
#override
void initState() {
super.initState();
getPosts();
_scrollController?.addListener(() {
double maxScroll = _scrollController.position.maxScrollExtent;
double currentScroll = _scrollController.position.pixels;
double delta = MediaQuery.of(context).size.height * 0.25;
if (maxScroll - currentScroll <= delta) {
getPosts();
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(
'Admin App',
style: TextStyle(fontWeight: FontWeight.w900),
),
leading: IconButton(
onPressed: () => Navigator.push(
context, MaterialPageRoute(builder: (_) => CreatePost())),
icon: Icon(Feather.plus)),
centerTitle: true,
),
body: isLoading
? circularProgress(context)
: ListView.builder(
controller: _scrollController,
itemCount: post.length,
itemBuilder: (context, index) {
internetChecker(context);
PostModel posts = PostModel.fromJson(post[index].data());
return Padding(
padding: const EdgeInsets.all(10.0),
child: Posts(post: posts),
);
},
),
);
}
internetChecker(context) async {
bool result = await DataConnectionChecker().hasConnection;
if (result == false) {
showInSnackBar('No Internet Connection', context);
}
}
void showInSnackBar(String value, context) {
ScaffoldMessenger.of(context).removeCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(value)));
}
}

Why don't you try using a Refresh Indicator Widget for this purpose. Check sample usage in the Flutter official documentation

Related

Flutter - The operator '[]' isn't defined for the type 'Object'. - record not displayed with the widget

I am trying to do a compound query. If this one works, then I want to display the results in a listView.builder.
The records are not displayed properly. I do not understand why. I must display the name field of each document find using the query. I have tried different options, but the result is also an error.
Thank you.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class LoadDataFromFirestore extends StatefulWidget {
const LoadDataFromFirestore({super.key});
#override
State<LoadDataFromFirestore> createState() => _LoadDataFromFirestoreState();
}
class _LoadDataFromFirestoreState extends State<LoadDataFromFirestore> {
QuerySnapshot? querySnapshot;
#override
void initState() {
super.initState();
myQuery().then((results) {
setState(() {
querySnapshot = results;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _showDrivers(),
);
}
Widget _showDrivers() {
if (querySnapshot != null) {
return ListView.builder(
primary: false,
itemCount: querySnapshot?.docs.length,
padding: const EdgeInsets.all(12),
itemBuilder: (context, i) {
return Column(
children: <Widget>[
Text(querySnapshot!.docs[i].data().toString()),
],
);
},
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
}
Future myQuery () async{
final uid = FirebaseAuth.instance.currentUser!.uid;
final path = 'Users/$uid/allTasks';
final currentQuery = FirebaseFirestore.instance.collection(path);
Query statusQuery = currentQuery.where('status', isEqualTo: 'Next Action');
Query importantQuery = statusQuery.where('important', isEqualTo: 'False');
final snapshot = await importantQuery.get();
final data = snapshot.docs;
if(data.isNotEmpty){
for(var i =0; i < data.length; i++){
print(data[i].data());
return Text(data[i]['name']);
}
return data;
}
}
}
Since I can't test this, my guess is that the view is not refreshing properly since you used an function to get that widget.
Also you're returning an widget (Text) instead of the expected response that is of type QuerySnapshot.
Also you're returning data at the end which is a List and not the snapshot.
Go ahead and try this please:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class LoadDataFromFirestore extends StatefulWidget {
const LoadDataFromFirestore({super.key});
#override
State<LoadDataFromFirestore> createState() => _LoadDataFromFirestoreState();
}
class _LoadDataFromFirestoreState extends State<LoadDataFromFirestore> {
QuerySnapshot? querySnapshot;
#override
void initState() {
super.initState();
myQuery().then((results) {
setState(() {
querySnapshot = results;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: querySnapshot != null ? ListView.builder(
primary: false,
itemCount: querySnapshot?.docs.length,
padding: const EdgeInsets.all(12),
itemBuilder: (context, i) {
return ListTile(
title: Text(querySnapshot!.docs[i].data().toString()),
);
},
) : Center(
child: CircularProgressIndicator(),
)
);
}
Future myQuery () async{
final uid = FirebaseAuth.instance.currentUser!.uid;
final path = 'Users/$uid/allTasks';
final currentQuery = FirebaseFirestore.instance.collection(path);
Query statusQuery = currentQuery.where('status', isEqualTo: 'Next Action');
Query importantQuery = statusQuery.where('important', isEqualTo: 'False');
final snapshot = await importantQuery.get();
final data = snapshot.docs;
if(data.isNotEmpty){
//for(var i =0; i < data.length; i++){
// print(data[i].data());
// return Text(data[i]['name']);
//}
return snapshot;
}
}
}

Is there any solution for my code in Flutter?

I want to render all of the pdfs' first page as images and pass them to Home Screen. I make Splash Screen's duration to 30second. But I think it is not right because there can be hundreds of pdfs in someone's phone storage and Splash Screen's duration can be longer than 30seconds. So is there any solution to my problem? Here is my code. Enlighten me pls.
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:native_pdf_renderer/native_pdf_renderer.dart';
import 'package:splash_screen_view/SplashScreenView.dart';
import 'constant.dart';
import 'package:permission_handler/permission_handler.dart';
import 'home_screen.dart';
class Splashscreens extends StatefulWidget {
_SplashscreensState createState() => _SplashscreensState();
}
class _SplashscreensState extends State<Splashscreens> {
List<FileSystemEntity>? filepdf;
List<Uint8List>? imagepdf = [];
void initState() {
super.initState();
getFile();
}
getFile() async {
await Permission.storage.request();
final myDir = Directory('/storage/emulated/0/documents/');
filepdf = myDir.listSync(recursive: true, followLinks: true);
for (int index = 0; index < filepdf!.length; index++) {
final document = await PdfDocument.openFile(filepdf![index].path);
final page = await document.getPage(1);
final pageImage = await page.render(width: page.width, height: page.height);
setState(() {
imagepdf!.add(pageImage!.bytes);
});
}
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: SplashScreenView(
navigateRoute: HomeScreen(filepdf, imagepdf),
duration: 25000,
imageSize: 650,
imageSrc: "assets/image/jensenpdfviewerlogo.jpg",
colors: [
Colors.purple,
Colors.blue,
Colors.yellow,
Colors.red,
Colors.orange,
Color(0xFFECECEC)
],
pageRouteTransition: PageRouteTransition.SlideTransition,
text: "LOADING......",
textType: TextType.ColorizeAnimationText,
textStyle: fontStyle,
backgroundColor: Color(0xFF4E4AC2),
),
),
);
}
}
Here is my suggestion
class Splashscreens extends StatefulWidget {
_SplashscreensState createState() => _SplashscreensState();
}
class _SplashscreensState extends State<Splashscreens> {
List<FileSystemEntity>? filepdf;
List<Uint8List>? imagepdf = [];
Future<Map<String, dynamic>> getFile() async {
await Permission.storage.request();
final myDir = Directory('/storage/emulated/0/documents/');
filepdf = myDir.listSync(recursive: true, followLinks: true);
for (int index = 0; index < filepdf!.length; index++) {
final document = await PdfDocument.openFile(filepdf![index].path);
final page = await document.getPage(1);
final pageImage = await page.render(width: page.width, height: page.height);
setState(() {
imagepdf!.add(pageImage!.bytes);
});
}
var data = {
"file_pdf": filepdf,
"image_pdf": imagepdf
};
return data;
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getFile(),
builder: (BuildContext context, AsyncSnapshot<Map<String, dynamic>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
case ConnectionState.done:
Map<String, dynamic> data = snapshot.data!;
List<FileSystemEntity>? _filePdf = data["file_pdf"];
List<Uint8List>? _imagepdf = data["image_pdf"];
return YourResultScreen();
default:
return Container();
}
}
);
}
}

how to make this lazyload scrolling working with provider

it take about 7 days trying make a working example for lazyload listview with provider in flutter with real world example and it's still not working because i think something is missing
As a note : the first load , works good and when i scroll it's print (scroll) but nothing happened it's still in the same page
if i try to return _todolist variable in the _onScrollUpdated it not change page correctly and after three times i see this error
E/flutter ( 7713): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)]
Unhandled Exception: type 'String' is not a subtype of type
'List' E/flutter ( 7713): #0 TodoService.fetchTodos
(package:flutter_todo_provider/services/todo_service.dart:32:21)
json example
https://jsonformatter.org/52c83e
todos_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_todo_provider/helpers/http_exception.dart';
import 'package:provider/provider.dart';
import 'package:flutter_todo_provider/.env.dart';
import 'package:flutter_todo_provider/services/todo_service.dart';
class TodosScreen extends StatefulWidget {
#override
_TodosScreenState createState() => _TodosScreenState();
}
class _TodosScreenState extends State<TodosScreen> {
ScrollController _controller;
List<dynamic> _todoList;
bool _isLoading ;
#override
void initState() {
super.initState();
_controller = ScrollController();
_controller.addListener(_onScrollUpdated);
}
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Configuration.AppName),
),
body: FutureBuilder(
future: _fetchListItems(),
builder: (context, snapshot){
if(snapshot.hasData){
return _listItems(snapshot.data);
}
return _buildProgressIndicator();
}
),
);
}
_fetchListItems() async {
try {
await Provider.of<TodoService>(context, listen: false).loadNextPage();
_todoList = Provider.of<TodoService>(context, listen: false).items;
} on HttpException catch (e) {
EasyLoading.showError(e.message);
}
return _todoList ;
}
Widget _listItems(data){
_isLoading = Provider.of<TodoService>(context, listen: false).isLoading ;
return ListView.builder(
controller: _controller,
itemCount: data.length ,
itemBuilder: (context, index) {
return ListTile(
title: Text(data[index].title),
subtitle:Text(data[index].content),
trailing: Icon(Icons.print),
);
},
);
}
Future<void> _onScrollUpdated() async {
print("Scroll11");
var maxScroll = _controller.position.maxScrollExtent;
var currentPosition = _controller.position.pixels;
if (currentPosition == maxScroll ) {
try {
await Provider.of<TodoService>(context, listen: false).loadNextPage();
_todoList = Provider.of<TodoService>(context, listen: false).items;
// return _todoList ; if use this line i see the error
} on HttpException catch (e) {
EasyLoading.showError(e.message);
}
}
}
Widget _buildProgressIndicator() {
_isLoading = Provider.of<TodoService>(context, listen: false).isLoading ;
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: _isLoading ? 1.0 : 00,
child: new CircularProgressIndicator(),
),
),
);
}
}
todo_service.dart
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_todo_provider/.env.dart';
import 'package:flutter_todo_provider/models/todo.dart';
class TodoService with ChangeNotifier {
bool isLoading = false;
bool isFetching = false;
int currentPage = 1;
int totalRows = 10;
List<Todo> items = [];
loadNextPage() async {
await fetchTodos(currentPage);
currentPage++;
notifyListeners();
}
Future fetchTodos(int currentPage) async {
try {
//404
var options = Options(headers: {
HttpHeaders.authorizationHeader: 'Basic ${Configuration.authToken}'
});
Map<String, dynamic> qParams = {
'current_page': currentPage,
};
Response response = await Dio().get('${Configuration.ApiUrl}/todos/my_todos', options: options, queryParameters: qParams);
List<dynamic> responseBode = response.data["data"];
responseBode.forEach(( dynamic json) {
items.add(Todo.fromJson(json));
});
notifyListeners();
} on DioError catch (e) {
print("Error Message" + e.response.statusMessage);
return items=[];
}
}
}
Here is the code:
class TodoScreen extends StatefulWidget {
// Your service goes here
// (the class extending ChangeNotifier)
#override
_TodoScreenState createState() => _TodoScreenState();
}
class _TodoScreenState extends State<TodoScreen> {
final TodoService todoService = TodoService();
ScrollController _controller;
#override
void initState() {
super.initState();
_controller = ScrollController();
_controller.addListener(_onScrollUpdated);
loadNextPage();
}
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Configuration.AppName'),
),
body: ChangeNotifierProvider.value(
value: todoService,
child: Consumer<TodoService>(builder: (_, ctl, __) {
if (todoService.isLoading) {
return _buildProgressIndicator();
} else {
return _listItems(todoService.items);
}
}),
),
);
}
Widget _listItems(data) {
return ListView.builder(
controller: _controller,
itemCount: data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(data[index].title),
subtitle: Text(data[index].content),
trailing: Icon(Icons.print),
);
},
);
}
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: CircularProgressIndicator(),
),
);
}
Future<void> _onScrollUpdated() async {
var maxScroll = _controller.position.maxScrollExtent;
var currentPosition = _controller.position.pixels;
if (currentPosition == maxScroll) {
todoService.loadNextPage();
}
}
}
Note that i didn't make changes to your service. The notifyListeners will do all the job for us.
When you are using Provider, the idea is to keep all your data inside the controller or service (the class that extends ChangeNitifier) and just use the variables with notifyListeners to change the behavior of your screen.
The screen needs to be listening for changes, for this we use the pair ChangeNotifierProvider.value with Consumer(builder: (_, ctl, __) {}).
Use ChangeNotifierProvider in some upper level of the widget tree and use Consumer only where you need the widget to be updated. You can even use more than one Consumer, they all just need to be under ChangeNotifierProvider.

How to place a key in ListView to maintain state on page change

I am trying to build a steam of posts (think twitter or instagram type posts) that a user is able to scroll through. As they are scrolling, they can click one of the posts and navigate to a new page. When they go navigate back from that page, I want them to remain at the same position on the position that they were previously on within the ListView.
PROBLEM
I can not currently keep the stream widget from rebuilding and returning to the scroll position. I know that one of the solutions to this is to include a key; however, I have tried to including the key in the ListView.builder, but it has not worked.
QUESTION
Where should I include the key? Am I using the right type of key?
class Stream extends StatefulWidget {
Stream({Key key, this.user}) : super(key: key);
final User user;
#override
_StreamState createState() => new _StreamState(
user: user
);
}
class _StreamState extends State<Stream> {
_StreamState({this.user});
final User user;
Firestore _firestore = Firestore.instance;
List<DocumentSnapshot> _posts = [];
bool _loadingPosts = true;
int _per_page = 30;
DocumentSnapshot _lastPosts;
ScrollController _scrollController = ScrollController();
bool _gettingMorePosts = false;
bool _morePostsAvailable = true;
_getPosts() async {
Query q = _firestore
.collection('posts')
.document(user.user_id)
.collection('posts')
.orderBy("timePosted", descending: true)
.limit(_per_page);
setState(() {
_loadingPosts = true;
});
QuerySnapshot querySnapshot = await q.getDocuments();
_posts = querySnapshot.documents;
if (_posts.length == 0) {
setState(() {
_loadingPosts = false;
});
}
else {
_lastPosts = querySnapshot.documents[querySnapshot.documents.length - 1];
setState(() {
_loadingPosts = false;
});
}
}
_getMorePosts() async {
if (_morePostsAvailable == false) {
return;
}
if (_gettingMorePosts == true) {
return;
}
if (_posts.length == 0) {
return;
}
_gettingMorePosts = true;
Query q = _firestore
.collection('posts')
.document(user.user_id)
.collection('posts')
.orderBy("timePosted", descending: true)
.startAfter([_lastPosts.data['timePosted']]).limit(_per_page);
QuerySnapshot querySnapshot = await q.getDocuments();
if (querySnapshot.documents.length == 0) {
_morePostsAvailable = false;
}
if(querySnapshot.documents.length > 0) {
_lastPosts = querySnapshot.documents[querySnapshot.documents.length - 1];
}
_posts.addAll(querySnapshot.documents);
setState(() {});
_gettingMorePosts = false;
}
#override
void initState() {
super.initState();
_getPosts();
_scrollController.addListener(() {
double maxScroll = _scrollController.position.maxScrollExtent;
double currentScroll = _scrollController.position.pixels;
double delta = MediaQuery.of(context).size.height * 0.25;
if (maxScroll - currentScroll < delta) {
_getMorePosts();
}
});
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
new Expanded(
child: _loadingPosts == true
? Container(
child: Center(
child: Text(" "),
),
)
: Container(
child: Center(
child: _posts.length == 0
? Center(
child: Text("Follow friends", style: TextStyle(fontSize: 15),),
)
: ListView.builder(
key: widget.key,
controller: _scrollController,
itemCount: _posts.length,
itemBuilder: (BuildContext ctx, int index) {
return new Widget(
//paramenters to build the post widget here
);
}),
),
),
),
],
);
}
One thing to note, since I don't want to return all pages (due to Firestore expenses calling so many posts), the build logic is created such that more posts are loaded upon scroll. I realize this may impact it.
Short answer:
You need to provide key to your ListView.builder like this:
ListView.builder(
key: PageStorageKey("any_text_here"),
// ...
)
Long answer:
You can see that when you come back from screen 2 to screen 1, the item 30 remains on the top.
Sorry it was difficult to reproduce your code due to limited availability to the variables you're using. I created a simple example to demonstrate what you're looking for.
Full code:
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView.builder(
key: PageStorageKey("any_text_here"), // this is the key you need
itemCount: 50,
itemBuilder: (_, i) {
return ListTile(
title: Text("Item ${i}"),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => DetailPage(index: i))),
);
},
),
);
}
}
class DetailPage extends StatefulWidget {
final int index;
const DetailPage({Key key, this.index}) : super(key: key);
#override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text(
"You clicked ${widget.index}",
style: Theme.of(context).textTheme.headline,
),
),
);
}
}
This kind of key works:
key: PageStorageKey('Your Key Name'),

Flutter load limited(20 documents) records and display in list view at a time

I have a requirement to read only 20 records at a time from Firestore and display in list view. When user click next button then I need to load next 20 documents. It is similar to paging.
Can some one help on how to do this. Any library available for this?
The below code limits the number of records to 3, it will load more records while scrolling the list instead of load more button starting from the last document retrieved from the cloud store
class Feed extends StatefulWidget {
Feed({this.firestore});
final Firestore firestore;
#override
_FeedState createState() => _FeedState();
}
class _FeedState extends State<Feed> {
ScrollController controller;
DocumentSnapshot _lastVisible;
bool _isLoading;
CollectionReference get homeFeeds => widget.firestore.collection('homefeed');
List<DocumentSnapshot> _data = new List<DocumentSnapshot>();
final scaffoldKey = GlobalKey<ScaffoldState>();
#override
void initState() {
controller = new ScrollController()..addListener(_scrollListener);
super.initState();
_isLoading = true;
_getData();
}
Future<Null> _getData() async {
// await new Future.delayed(new Duration(seconds: 5));
QuerySnapshot data;
if (_lastVisible == null)
data = await widget.firestore
.collection('homefeed')
.orderBy('created_at', descending: true)
.limit(3)
.getDocuments();
else
data = await widget.firestore
.collection('homefeed')
.orderBy('created_at', descending: true)
.startAfter([_lastVisible['created_at']])
.limit(3)
.getDocuments();
if (data != null && data.documents.length > 0) {
_lastVisible = data.documents[data.documents.length - 1];
if (mounted) {
setState(() {
_isLoading = false;
_data.addAll(data.documents);
});
}
} else {
setState(() => _isLoading = false);
scaffoldKey.currentState?.showSnackBar(
SnackBar(
content: Text('No more posts!'),
),
);
}
return null;
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
appBar: new AppBar(),
body: RefreshIndicator(
child: ListView.builder(
controller: controller,
itemCount: _data.length + 1,
itemBuilder: (_, int index) {
if (index < _data.length) {
final DocumentSnapshot document = _data[index];
return new Container(
height: 200.0,
child: new Text(document['question']),
);
}
return Center(
child: new Opacity(
opacity: _isLoading ? 1.0 : 0.0,
child: new SizedBox(
width: 32.0,
height: 32.0,
child: new CircularProgressIndicator()),
),
);
},
),
onRefresh: ()async{
_data.clear();
_lastVisible=null;
await _getData();
},
),
);
}
#override
void dispose() {
controller.removeListener(_scrollListener);
super.dispose();
}
void _scrollListener() {
if (!_isLoading) {
if (controller.position.pixels == controller.position.maxScrollExtent) {
setState(() => _isLoading = true);
_getData();
}
}
}
}
hope it helps