fquery icon indicating copy to clipboard operation
fquery copied to clipboard

Feature or Bug? Widget seems to rebuild twice

Open du-nt opened this issue 1 year ago • 3 comments

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;
}

image null value appears 2 times What is the reason?

du-nt avatar Nov 10 '24 05:11 du-nt

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.

41y08h avatar Nov 16 '24 12:11 41y08h

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?

ryanovas avatar Dec 18 '24 21:12 ryanovas

Will look into this soon

41y08h avatar Mar 02 '25 19:03 41y08h

the commit a6aed5b solved the issue...

41y08h avatar Mar 28 '25 10:03 41y08h