I am trying to implement a infinite scroll pagnation in Flutter using provider for state management.
I have two page, HomePage() and TestingPage().On start up, the code is able to successfully load data from the api endpoint but the retrieved data was not displayed by the listview builder.The data is only displayed if i switch to the TestingPage() and back to HomePage().
The code will load more data if i reach the end of the scroll view, but the same problem is happening again, list view builder will not display the newly loaded data. It will only display it if i switch to TestingPage() and back to HomePage().
Reposting this because i made a mistake of posting the wrong code.
// UserInfo.dart
class UserInfo {
int userId;
int id;
String title;
String body;
UserInfo(this.userId, this.id, this.title, this.body);
factory UserInfo.fromJson(Map<String, dynamic> json) {
final userId = json['userId'];
final id = json['id'];
final title = json['title'];
final body = json['body'];
return UserInfo(userId, id, title, body);
}
#override
String toString() {
return "id: $id";
}
}
// user_info_response.dart
import 'package:end_point/models/UserInfo.dart';
class ListOfUserInfoResponse {
int? code;
List<UserInfo> listOfUserInfo;
ListOfUserInfoResponse({this.code, required this.listOfUserInfo});
factory ListOfUserInfoResponse.fromJson(int statusCode, List<dynamic> json) {
List<dynamic> list = json;
return ListOfUserInfoResponse(
code: statusCode,
listOfUserInfo: list.map((i) => UserInfo.fromJson(i)).toList());
}
}
// user_info_api.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:end_point/models/API/user_info_response.dart';
Future<ListOfUserInfoResponse> getListOfUserInfo(page) async {
final response = await http.get(Uri.parse(
"https://jsonplaceholder.typicode.com/posts?_limit=15&_page=$page"));
if (response.statusCode == 200) {
return ListOfUserInfoResponse.fromJson(
response.statusCode, jsonDecode(response.body));
} else {
throw Exception("Failed to load data");
}
}
// user_info_provider.dart
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:end_point/models/UserInfo.dart';
import 'package:end_point/api/user_info_api.dart' as UserInfoAPI;
import 'package:end_point/models/API/user_info_response.dart';
class UserInfoProvider with ChangeNotifier {
int _page = 1;
int get page => _page;
bool _isLoading = false;
bool get isLoading => _isLoading;
List<UserInfo> _listOfUserData = [];
List<UserInfo> get listOfUserData => _listOfUserData;
set listOfUserData(List<UserInfo> listOfUserData) {
_listOfUserData = listOfUserData;
notifyListeners();
}
bool _hasMore = true;
bool get hasMore => _hasMore;
Future<void> loadMoreData() async {
if (_isLoading) return;
_isLoading = true;
ListOfUserInfoResponse response =
await UserInfoAPI.getListOfUserInfo(_page);
_listOfUserData.addAll(response.listOfUserInfo);
_page++;
_isLoading = false;
if (response.listOfUserInfo.length < 20) {
_hasMore = false;
}
notifyListeners();
}
void refreshData() async {
_isLoading = false;
_hasMore = true;
_page = 1;
listOfUserData.clear();
notifyListeners();
}
}
// home.dart
import 'package:end_point/providers/user_info_provider.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'dart:developer';
class HomePage extends StatefulWidget {
const HomePage({super.key});
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final _scrollController = ScrollController();
late UserInfoProvider userInfoProvider;
#override
void initState() {
super.initState();
userInfoProvider = Provider.of<UserInfoProvider>(context, listen: false);
userInfoProvider.loadMoreData();
_scrollController.addListener(_onScroll);
}
Future<void> _onScroll() async {
if (_scrollController.position.maxScrollExtent ==
_scrollController.offset) {
await userInfoProvider.loadMoreData();
}
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
Future refresh() async {
userInfoProvider.refreshData();
await userInfoProvider.loadMoreData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("END POINT LEGGO")),
body: RefreshIndicator(
key: UniqueKey(),
onRefresh: refresh,
child: ListView.builder(
key: const PageStorageKey<String>('HP'),
itemCount: userInfoProvider.listOfUserData.length,
controller: _scrollController,
shrinkWrap: true,
itemBuilder: (context, index) {
final id = userInfoProvider.listOfUserData[index].id;
if (index < userInfoProvider.listOfUserData.length) {
return Padding(
padding: const EdgeInsets.all(2.0),
child: Container(
height: 50,
width: 200,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: Colors.red),
child: ListTile(title: Text('$id'))),
);
} else {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 32),
child: Center(
child: userInfoProvider.hasMore
? const CircularProgressIndicator()
: const Text('No more data to load'),
));
}
}),
));
}
I found the fix to this problem, #home.dart instead of using the UserInfoProvider instance that was declared in the initState().
Simply declare a new userInfoProvider instance inside the Widget build().
E.g. final data = Provider.of(context);
From what I understood, this is because the instance of the UserInfoProvider that was declared in the initState() has listen: false. false value will mean that any value changes, will not trigger a rebuild of the widget.
Related
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;
}
}
}
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();
}
}
);
}
}
I am building a food recipe app where user can browse various recipes.
The functionality is that, when user hit delete button, the item will not be shown in listing. I navigated the the mealId to the previous screen, i.e. Listing screen through
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).pop(mealId);
},
child: const Icon(Icons.delete),
),
I receive the pop() value in backward widget like:
void selectMeal(BuildContext context) {
Navigator.of(context)
.pushNamed(MealsDetailsScreen.routeName, arguments: id)
.then((result) {
if (result != null) {
removeItem(result);
print(result); // it prints the expected id
}
});
}
And in the code attached fully, I wanted to remove the item details via mealId
void _removeMeal(String mealId) {
setState(() {
print("$mealId from didChangedDependancies"); //it also prints the expected id
displayedMeals.removeWhere((meal) => meal.id == mealId);
});
}
The code where I set the function to remove:
import 'package:flutter/material.dart';
import '../models/meals.dart';
import '../models/dummy_data.dart';
import '../widgets/meal_item.dart';
class CategoryMealaScreen extends StatefulWidget {
static const routeName = '/category-meals';
#override
State<CategoryMealaScreen> createState() => _CategoryMealaScreenState();
}
class _CategoryMealaScreenState extends State<CategoryMealaScreen> {
late String categoryTitle;
late List<Meal> displayedMeals;
var _loadedInitData = false;
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
if (!_loadedInitData) {
final routeArgs =
ModalRoute.of(context)!.settings.arguments as Map<String, String>;
categoryTitle = routeArgs['title'].toString();
final categoryId = routeArgs['id'];
displayedMeals = dummyMeals.where((meal) {
return meal.categories.contains(categoryId);
}).toList();
_loadedInitData = true;
}
super.didChangeDependencies();
}
void _removeMeal(String mealId) {
setState(() {
print("$mealId from didChangedDependancies");
displayedMeals.removeWhere((meal) => meal.id == mealId);
});
}
#override
Widget build(BuildContext context) {
final routeArgs = // received data from widget CategoryItems()
ModalRoute.of(context)!.settings.arguments as Map<String, String>;
final categoryTitle = routeArgs['title'];
final categoryId = routeArgs['id'];
final displayedMeals = dummyMeals.where((meal) {
return meal.categories.contains(categoryId);
}).toList();
return Scaffold(
appBar: AppBar(
title: Text(categoryTitle.toString()),
),
body: ListView.builder(
itemCount: displayedMeals.length,
itemBuilder: (ctx, index) {
return MealItem(
id: displayedMeals[index].id,
title: displayedMeals[index].title,
imageUrl: displayedMeals[index].imageUrl,
complexity: displayedMeals[index].complexity,
affordability: displayedMeals[index].affordability,
duration: displayedMeals[index].duration,
removeItem: _removeMeal,
);
}),
);
}
}
No error shows on console.
I'll be vary happy if you guys help me out! Thanks a lot😎
Remove final displayedMeals inside your build method.
Use the displayedMeals variable outside your build method instead.
I wanna fetch data from PhpMyAdmin. But i am facing one issue. I can see in the body my data. I am sharing my source code. Thx for helping. And I am working with flutter desktop.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class WarehousePage extends StatefulWidget {
const WarehousePage({Key key}) : super(key: key);
#override
_WarehousePageState createState() => _WarehousePageState();
}
class _WarehousePageState extends State<WarehousePage> {
List data = [];
#override
void initState() {
fetchData();
super.initState();
}
void fetchData() async {
final response =
await http.get(Uri.parse('url/getdata.php'));
data = json.decode(response.body);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int index) => ListTile(
title: Text(data[index]['productName']),
),
),
);
}
}
Update my code but still same. What is wrong? I have not any model class. I am see my data in the body and debug console. But i cant write it in the listview.
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class WarehousePage extends StatefulWidget {
const WarehousePage({Key key}) : super(key: key);
#override
_WarehousePageState createState() => _WarehousePageState();
}
class _WarehousePageState extends State<WarehousePage> {
List data = [];
#override
void initState() {
fetchData();
super.initState();
}
Future<List> fetchData() async {
var jsonResponse;
try {
var url = Uri.parse('https://rul/getdata.php');
var response = await http.get(url).timeout(const Duration(seconds: 20));
if (response.statusCode == 200) {
jsonResponse = json.decode(response.body);
if (jsonResponse != null) {
return (json.decode(response.body) as List)
.map((data) => (data))
.toList();
}
}
} on SocketException catch (e) {
print(e);
} catch (e) {
print(e);
}
return jsonResponse;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int index) => ListTile(
title: Text(data[index]['productName']),
),
),
);
}
}
static Future<List<SomeModel>> fetchQuizTest() async {
var jsonResponse;
try {
var url = Uri.parse(url');
var response = await http.get(url).timeout(const Duration(seconds: 20));
if (response.statusCode == 200) {
jsonResponse = json.decode(response.body);
if (jsonResponse != null) {
return (json.decode(response.body) as List)
.map((data) => new SomeModel.fromJson(data))
.toList();
}
}
} on SocketException catch (e) {
print(e);
} catch (e) {
print(e);
}
return jsonResponse;
}
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.