Feature or Bug? Widget seems to rebuild twice
I ran the following code and logged the query result
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fquery/fquery.dart';
import 'models/post_model.dart';
Future<List<Post>> getPosts() async {
final res =
await Dio().get('https://jsonplaceholder.typicode.com/posts?_limit=3');
return (res.data as List)
.map((e) => Post.fromJson(e as Map<String, dynamic>))
.toList();
}
void main() {
runApp(QueryClientProvider(
queryClient: QueryClient(
defaultQueryOptions: DefaultQueryOptions(
cacheDuration: const Duration(minutes: 20),
refetchInterval: const Duration(minutes: 5),
refetchOnMount: RefetchOnMount.always,
staleDuration: const Duration(minutes: 3),
),
),
child: const MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends HookWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
Widget build(BuildContext context) {
final posts = useQuery(['posts'], getPosts);
print('${posts.data}');
if (posts.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (posts.isError) {
return Center(child: Text(posts.error!.toString()));
}
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(title),
),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class Post {
int userId;
int id;
String title;
String body;
Post({
required this.userId,
required this.id,
required this.title,
required this.body,
});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
userId: json['userId'] as int,
id: json['id'] as int,
title: json['title'] as String,
body: json['body'] as String,
);
}
@override
String toString() =>
"Post(userId: $userId,id: $id,title: $title,body: $body)";
@override
int get hashCode => Object.hash(userId, id, title, body);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Post &&
runtimeType == other.runtimeType &&
userId == other.userId &&
id == other.id &&
title == other.title &&
body == other.body;
}
null value appears 2 times
What is the reason?
Thanks for using fquery.
This is not a bug, there are certain variables like isFetching and status in the result of useQuery and whenever that changes, as in your case, there's a notification that flows to the widget and it gets rebuilt.
I am also experiencing this. It's not just the status variables, the isLoading state is reset and the loading icon will come back for a second time. It doesn't happen consistently, but it almost seems like the result of the future isn't being cached and if the widget rebuilds for some reason it re-runs the query and shows the loading status again which leads to the loading icon flashing twice instead of once on first load.
My fquery widget is as close to the top of the app as possible, just a safearea and scrollchildsingleview above it but I can still see it get rebuilt at least once and fquery resets it's state:
class PuzzleUI extends HookWidget {
final String slug;
PuzzleUI({
Key? key,
required this.slug,
}) : super(key: key);
final DateTime userStartTime = DateTime.now();
@override
Widget build(BuildContext context) {
final puzzle = useQuery<PuzzleData, DioException>(
['puzzle', slug], () => getPuzzle(slug));
return Builder(
builder: (context) {
if (puzzle.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (puzzle.isError) {
final String errorMessage = puzzle.error?.response?.statusCode == 404
? "This puzzle doesn't exist. Please double check the URL for typos."
: 'An error occurred getting the puzzle from the server. Please refresh and retry.';
return Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Text(
errorMessage,
),
),
);
}
return ChangeNotifierProvider(
create: (context) => PuzzleModel(puzzle.data!, userStartTime),
child: const MarginConstrainedBox(child: PuzzleTabController()),
);
},
);
}
}
I really like this tool but multiple loads is kind of a dealbreaker and will make me fallback to future provider or something directly. Is there a way to fix this?
Will look into this soon
the commit a6aed5b solved the issue...