'Why Pull to Refresh does not stop?
I am caching data online from API and then store it in Have DB. Everything works well until when it comes to refreshing the data by Pull to Refresh widget. When I pull the screen down to refresh the data, the CircularProgressIndicator widget starts circling endlessly, so the data does not update. Please look at the code provided below and let me know what the problem is. Thanks!
Code:
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
List stories = [];
late Box storyBox;
@override
void initState() {
// TODO: implement initState
super.initState();
}
Future<void> openBox() async {
final document = await getApplicationDocumentsDirectory();
Hive.init(document.path);
Hive.registerAdapter<Item>(ItemAdapter());
storyBox = await Hive.openBox<Item>(storyBoxName);
return;
}
Future<bool> getAllData() async {
await openBox();
const url = 'https://shaparak-732ff.firebaseio.com/items.json';
try {
final response = await http.get(Uri.parse(url));
final extractedData = json.decode(response.body) as Map<String, dynamic>;
await putData(extractedData);
} catch (SocketException) {
print('NO Internet');
}
var mymap = storyBox.toMap().values.toList().reversed.toList();
if (mymap.isEmpty) {
stories.add('Empty');
} else {
stories = mymap;
}
return Future.value(true);
}
Future putData(Map<String, dynamic> data) async {
await storyBox.clear();
Item newStory;
data.forEach((key, value) {
newStory = Item(
title: value['title'],
category: value['category'],
content: value['content'],
author: value['author'],
id: key.toString(),
date: DateTime.parse(value['id']),
);
storyBox.put(key, newStory);
});
}
Future<void> updateData() async {
const url = 'https://shaparak-732ff.firebaseio.com/items.json';
try {
final response = await http.get(Uri.parse(url));
final extractedData = json.decode(response.body) as Map<String, dynamic>;
await putData(extractedData);
setState(() {});
} catch (SocketException) {
Fluttertoast.showToast(
msg: "This is Center Short Toast",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0);
}
}
@override
void didChangeDependencies() {
// TODO: implement didChangeDependencies
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[300],
appBar: AppBar(
title: const Text('Shaparak'),
backgroundColor: Theme.of(context).primaryColor,
),
body: Center(
child: FutureBuilder(
future: getAllData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (stories.contains('empty')) {
return const Text('No Data');
} else {
return RefreshIndicator(
child: ItemsGrid(stories as List<Item>),
onRefresh: updateData);
}
} else {
return const CircularProgressIndicator();
}
},
),
),
);
}
}
Solution 1:[1]
FutureBuilder's snapshot parameter also contains a snapshot.hasError that tells you if an exception was raised inside the future function. You can check if snapshot.hasError is true and display a Text(snapshot.error) to see what the error was.
Right now you are not making use of this error if it is happening. My guess is that you are getting and ignoring an error there.
I bet getAllData() is not completing correctly. Do you have any errors in your logs? You can try to add print('here'); kind of lines at the end of getAllData() and see if it actually executes till the end.
Solution 2:[2]
When you call setState() in updateData() the build method is called. This causes your FutureBuilder to call getAllData() again.
Instead of calling getAllData() in your build method, call it in initState() at the beginning and save the data into the box.
Use a ValueListeneableBuilder to show the data directly from the box, and it will update when you update the box content
ValueListenableBuilder(
valueListenable: storyBox.listenable(),
builder: (context, box, widget) {
// TODO get stories from box
},
)
Solution 3:[3]
As a general rule of thumb, never call setState() in a Future function if you're going to be calling that function with a FutureBuilder in your build method because it WILL trigger an infinite loop.
More technically, setState will trigger a rebuild of the build function which means the FutureBuilder will be rebuilt and call that future you passed in that contains setState and setState will rebuild the build method again before that future completes which will start the FutureBuilder all over again, and it keeps going on like that forever...
To fix the endless loading:
In your updateData() method, remove setState() and everything else should work just fine.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Gazihan Alankus |
| Solution 2 | passsy |
| Solution 3 | David C |
