How To Dispose PagingController(infinite_scroll_pagination) In GetxController? - flutter

I want to release everything when leaving the current screen.
Getx advising me to use onClose method of GetxController for this.
#override
void onClose() {
pagingController.dispose();
super.onClose();
}
He kept his promise, so nice. But it didn't take long! Let's dive in;
The controller has a future method called _fetchPage, it basically fetches page data and works with pagingController
If I leave the screen before completing the _fetchPage, the future remains working. Once the data is fetched, pagingController is accessed but it has been already disposed.
And finally, it prints Unhandled Exception: Exception: A PagingController was used after being disposed.
Future<void> _fetchPage(int pageKey) async {
try {
var skip = pageKey == 0 ? 0 : (10 * pageKey);
var data = await fetchDataOfPage(skip, limit);
final isLastPage = data.length < limit;
if (isLastPage) {
pagingController.appendLastPage(data);
} else {
final nextPageKey = pageKey + 1;
pagingController.appendPage(data, nextPageKey);
}
} catch (error) {
pagingController.error = error;
}
}

I had this exact problem and was able to fix it by checking that the widget is still mounted after retrieving the data:
Future<void> _fetchPage(int pageKey) async {
try {
var skip = pageKey == 0 ? 0 : (10 * pageKey);
var data = await fetchDataOfPage(skip, limit);
// bail out if widget is no longer mounted
if (!mounted) {
return;
}
final isLastPage = data.length < limit;
if (isLastPage) {
pagingController.appendLastPage(data);
} else {
final nextPageKey = pageKey + 1;
pagingController.appendPage(data, nextPageKey);
}
} catch (error) {
pagingController.error = error;
}
}

Related

Flutter: _TypeError

I'm trying to get datas from api and add them a list. But at this moment, I see datas i got but I can't get it out of the function. What should i do?
function
List<dynamic> xxx = [];
#override
void initState() {
super.initState();
Future<List<dynamic>> fetchCompanies(List<dynamic> datas) async {
var response = await Dio().get(CompaniesPath().url);
if (response.statusCode == HttpStatus.ok) {
Map<String, dynamic> company = jsonDecode(response.data);
for (int i = 0; i < company['Data'].length; i++) {
datas.add(company['Data'][i]);
}
//print(datas); //=> I see datas here
} else {
throw Exception();
}
return datas;
}
print(fetchCompanies(xxx));
}
When I run print(fetchCompanies(xxx)); I got "Instance of 'Future<List<dynamic>>'". How can i get data inside fetchCompanies to my xxx list?
You're trying to print future instance of List that's why you got
Instance of Future<List>
You have to wait until function finish executing.
Catch here is you can't call wait in initState() so you have to use .then method
try this:
fetchCompanies(xxx)
.then((result) {
print("result: $result");
});
It should already work fine like it is. But you probably want to call a setState to refresh the page. Try this:
#override
void initState() {
super.initState();
Future<List<dynamic>> fetchCompanies(List<dynamic> datas) async {
var response = await Dio().get(CompaniesPath().url);
if (response.statusCode == HttpStatus.ok) {
Map<String, dynamic> company = jsonDecode(response.data);
for (int i = 0; i < company['Data'].length; i++) {
datas.add(company['Data'][i]);
}
//print(datas); //=> I see datas here
setState(() {}); // added this
} else {
throw Exception();
}
return datas;
}
print(fetchCompanies(xxx));
}

Unhandled Exception: Bad state: Tried to use PaginationNotifier after `dispose` was called

I have a StateNotifierProvider that calls an async function which loads some images from the internal storage and adds them to the AsyncValue data:
//Provider declaration
final paginationImagesProvider = StateNotifierProvider.autoDispose<PaginationNotifier, AsyncValue<List<Uint8List?>>>((ref) {
return PaginationNotifier(folderId: ref.watch(localStorageSelectedFolderProvider), itemsPerBatch: 100, ref: ref);
});
//Actual class with AsyncValue as State
class PaginationNotifier extends StateNotifier<AsyncValue<List<Uint8List?>>> {
final int itemsPerBatch;
final String folderId;
final Ref ref;
int _numberOfItemsInFolder = 0;
bool _alreadyFetching = false;
bool _hasMoreItems = true;
PaginationNotifier({required this.itemsPerBatch, required this.folderId, required this.ref}) : super(const AsyncValue.loading()) {
log("PaginationNotifier created with folderId: $folderId, itemsPerBatch: $itemsPerBatch");
init();
}
final List<Uint8List?> _items = [];
void init() {
if (_items.isEmpty) {
log("fetchingFirstBatch");
_fetchFirstBatch();
}
}
Future<List<Uint8List?>> _fetchNextItems() async {
List<AssetEntity> images = (await (await PhotoManager.getAssetPathList())
.firstWhere((element) => element.id == folderId)
.getAssetListRange(start: _items.length, end: _items.length + itemsPerBatch));
List<Uint8List?> newItems = [];
for (AssetEntity image in images) {
newItems.add(await image.thumbnailData);
}
return newItems;
}
void _updateData(List<Uint8List?> result) {
if (result.isEmpty) {
state = AsyncValue.data(_items);
} else {
state = AsyncValue.data(_items..addAll(result));
}
_hasMoreItems = _numberOfItemsInFolder > _items.length;
}
Future<void> _fetchFirstBatch() async {
try {
_numberOfItemsInFolder = await (await PhotoManager.getAssetPathList()).firstWhere((element) => element.id == folderId).assetCountAsync;
state = const AsyncValue.loading();
final List<Uint8List?> result = await _fetchNextItems();
_updateData(result);
} catch (e, stk) {
state = AsyncValue.error(e, stk);
}
}
Future<void> fetchNextBatch() async {
if (_alreadyFetching || !_hasMoreItems) return;
_alreadyFetching = true;
log("data updated");
state = AsyncValue.data(_items);
try {
final result = await _fetchNextItems();
_updateData(result);
} catch (e, stk) {
state = AsyncValue.error(e, stk);
log("error catched");
}
_alreadyFetching = false;
}
}
Then I use a scroll controller attached to a CustomScrollView in order to call fetchNextBatch() when the scroll position changes:
#override
void initState() {
if (!controller.hasListeners && !controller.hasClients) {
log("listener added");
controller.addListener(() {
double maxScroll = controller.position.maxScrollExtent;
double position = controller.position.pixels;
if ((position > maxScroll * 0.2 || position == 0) && ref.read(paginationImagesProvider.notifier).mounted) {
ref.read(paginationImagesProvider.notifier).fetchNextBatch();
}
});
}
super.initState();
}
The problem is that when the StateNotifierProvider is fetching more data in the async function fetchNextBatch() and I go back on the navigator (like navigator.pop()), Flutter gives me an error:
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Bad state: Tried to use PaginationNotifier after dispose was called.
Consider checking mounted.
I think that the async function responsible of loading data completes after I've popped the page from the Stack (which triggers a Provider dispose).
I'm probably missing something and I still haven't found a fix for this error, any help is appreciated.

API call terminate when screen gets off (or app goes background) IOS Flutter

I have a login page which starts downloading base data for the app after user enters username and password, and its a long duration operation like 2 or 3 minutes
In IOS at the middle of downloading data if screen gets off and locked, the operations terminates.
Here is the code
LoginPage part:
var repository = GlobalRestRepository();
var db = BasicDB();
List<basicModel> notDownloaded = await db.selectByLoaded(false);
for (int i = 0; i < notDownloaded.length; i++) {
await repository.getBasic(notDownloaded.elementAt(i));
}
GlobalRestRepository part:
class GlobalRestRepository {
final HttpClient http = HttpClient();
Future<void> getBasic(basicModel model) async {
String url = "${Variables.mainUrl + basicModelUrl}";
var response = await http.postExtraToken(url);
.
.
.
}
}
HttpClient part:
import 'package:http/http.dart';
...
class HttpClient {
static final HttpClient _instance = HttpClient._privateConstructor();
factory HttpClient() {
return _instance;
}
Future<dynamic> postExtraToken(String path) async {
Response response;
try {
response = await post(Uri.parse(path),
headers: {"extra": Variables.extra, "token": Variables.token});
final statusCode = response.statusCode;
if (statusCode >= 200 && statusCode < 299) {
if (response.body.isEmpty) {
return [];
} else {
return jsonDecode(utf8.decode(response.bodyBytes));
}
} else if (statusCode >= 400 && statusCode < 500) {
throw ClientErrorException();
} else if (statusCode >= 500 && statusCode < 600) {
throw ServerErrorException();
} else {
throw UnknownException();
}
} on SocketException {
throw ConnectionException();
}
}
}
Can anyone help me with this?
Using Wakelock plugin, we can solve this problem, but I don't know this is the best solution or not.
Note: This plugin works for all the platforms except linux.
Add the below code in the screen where you are initiating the api request.
#override
void didChangeDependencies() {
super.didChangeDependencies();
checkAndEnableWakeLock();
}
void checkAndEnableWakeLock() async {
bool wakeLockEnabled = await Wakelock.enabled;
if (!wakeLockEnabled) {
Wakelock.enable();
}
}
Call disable method when all the api calls are completed.
Wakelock.disable();
Using above code you can avoid screen getting off issue.

issue with geting all data from sqflite database

i have been trying to get all my data from a sqflite database, when i try to get a single data, this works totally fine:
Future<dynamic> getUser() async {
final db = await database;
var res = await db.query("files");
if (res.length == 0) {
return null;
} else {
var resMap = res[0];
return resMap;
}
}
but when i try to get all data using a for loop like the example below, i get an error
Future<dynamic> getUser() async {
final db = await database;
var res = await db.query("files");
var resMap;
var count = res.length;
if (count != 0) {
for (int i = 0; i < count; i++) {
resMap.add(res[i]);
}
}
return resMap;
}
the error says:
The method 'forEach' was called on null.
Receiver: null
Tried calling: forEach(Closure: (dynamic, dynamic) => Null)
i understand that it says that I've got no data,
and i also tried to remove the if statement, but still no luck!
change this method:
EDIT
Future<List<Map>> getUser() async {
final db = await database;
var res = await db.query("files");
List<Map> resMap = [];
if (res != null res.length > 0) {
for (int i = 0; i < count; i++) {
resMap.add(res[i]);
}
return resMap;
} else
{
return null;
}
}
try this in you widget
List<Map> newUser = [];
#override
void initState() {
super.initState();
getUser();
}
getUser() async {
final _userData = await DBProvider.db.getUser();
if(_userData != null ){
setState(() {
newUser = _userData;
});
} else{
setState(() {
newUser =[];
});
}
}

How to implement async/await in Flutter

I have this function that performs some firestore operations and retrieve the data.
But the problem is this returns an empty value. The reason I found was, it returns the value before fetching the data, it doesn't wait until it retrieves the data.
Here is my code. I have print some print statements to check the order of the execution.
getNewsOnSearchBar() {
final String _collection = 'news';
final Firestore _fireStore = Firestore.instance;
var newsList = [];
print("1");
getData() async {
print("2");
return await _fireStore.collection(_collection).getDocuments();
}
getData().then((val) async{
if (val.documents.length > 0) {
print("3");
for (int i = 0; i < val.documents.length; i++) {
newsList.add(await val.documents[i].data["headline"]);
}
} else {
print("Not Found");
}
});
print("4");
return ok;
}
And the output is:
I/flutter (17145): 1
I/flutter (17145): 2
I/flutter (17145): 4 // 4 prints before 3
I/flutter (17145): 3
Output I need is:
I/flutter (17145): 1
I/flutter (17145): 2
I/flutter (17145): 3
I/flutter (17145): 4
Can someone help me?
Instead of using then you can use await and that should solve your issue.
Here is how your code will look with changes and I strongly recommend to specify the type of data that your function will return.
getNewsOnSearchBar() async {
final String _collection = 'news';
final Firestore _fireStore = Firestore.instance;
var newsList = [];
print("1");
Future<QuerySnapshot> getData() async {
print("2");
return await _fireStore.collection(_collection).getDocuments();
}
QuerySnapshot val = await getData();
if (val.documents.length > 0) {
print("3");
for (int i = 0; i < val.documents.length; i++) {
newsList.add(val.documents[i].data["headline"]);
}
} else {
print("Not Found");
}
print("4");
return ok;
}
When you want to wait for a value from a Future as in your case, don't use then, use await:
Future<int> myFunc() {
return Future.delayed(Duration(seconds: 1), () => 0);
}
void main() async {
final int result = await myFunc();
print(result);//0
}
In your case, you would like to do something similar (you may change it according to your needs):
getNewsOnSearchBar() async {
final String _collection = 'news';
final Firestore _fireStore = Firestore.instance;
var newsList = [];
print("1");
getData() async {
print("2");
return await _fireStore.collection(_collection).getDocuments();
}
final ref = await getData();
if (ref.documents.length > 0) {
print("3");
for (int i = 0; i < ref.documents.length; i++) {
newsList.add(await ref.documents[i].data["headline"]);
}
} else {
print("Not Found");
}
print("4");
return ok;
}