0

I have a page that dynamically accepts a future list and a callback to get the future list to receive data and be able to refresh it through on refresh. a simplified version looks like this:

class ListSearchPage<T> extends StatefulWidget {

  final Future<List<T>> itemsFuture;
  final ValueGetter<Future<List<T>>> getItemsFuture;

  const ListSearchPage({Key key, this.getItemsFuture, this.itemsFuture})
      : super(key: key);

  @override
  _ListSearchPageState createState() => _ListSearchPageState();
}


class _ListSearchPageState<T> extends State<ListSearchPage> {
  Future<List<T>> itemsFuture;
  TextEditingController _controller;

  @override
  void initState() {
    itemsFuture = widget.itemsFuture;
    _controller = TextEditingController();
    super.initState();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
              future:
                  itemsFuture != null ? itemsFuture : widget.getItemsFuture(),
              builder: (context, snapshot) {

                   return RefreshIndicator(
                     onRefresh: () async {
                        setState(() {
                          itemsFuture = null;
                          _controller.text = '';
                        });
                      },
                     child: ...
                   );
                });
    }
}

So the first time, the page loads with the future already loaded. when the user refreshes, I mark the future as null so the callback gets called and the data can be re-fetched.

I'm trying to implement flutter_hooks throughout the app now and I've refactored this widget to be like this (simplified version):


class ListSearchPage<T> extends HookWidget {
  
  final Future<List<T>> itemsFuture;
  final ValueGetter<Future<List<T>>> getItemsFuture;

  const ListSearchPage({Key key, this.getItemsFuture, this.itemsFuture})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    final itemsFutureNotifier = useState(this.itemsFuture);
    final TextEditingController _controller = useTextEditingController();
    return FutureBuilder(
              future:
              itemsFutureNotifier.value != null ? itemsFutureNotifier.value : getItemsFuture(),
              builder: (context, snapshot) {
                   return RefreshIndicator(
                     onRefresh: () async {
                        itemsFutureNotifier.value = null;
                        _controller.text = '';
                      },
                     child: ...
                   );
                });
    }

}

This works the first time, however after that the value keeps on getting assigned to null, and therefore the value notifier does not get notified about the change. How can I force the widget to rebuild in this case like before? and as a bonus, do you see a better solution for this?

Thanks in advance.

update

This is itemsFuture

final future = useMemoized(() => repository.fetchData());

This is getItemsFuture

() => repository.fetchData()

The idea behind it is to fetch the data before the search page is opened. In my use case works.

I've found a solution to my problem, but I won't post it as an answer because I don't believe is clean and I rather see if someone finds the proper way of doing it.

current solution

@override
  Widget build(BuildContext context) {
    // feels like a dirty solution for rebuilding on refresh
    final counterNotifier = useState(0);
    final itemsFutureNotifier = useState(this.itemsFuture);
    final TextEditingController _controller = useTextEditingController();

 return ValueListenableBuilder(
            valueListenable: counterNotifier,
            builder: (context, value, child) {
return FutureBuilder(
              future:
              itemsFutureNotifier.value != null ? itemsFutureNotifier.value : getItemsFuture(),
              builder: (context, snapshot) {
                   return RefreshIndicator(
                     onRefresh: () async {
                        counterNotifier.value++;
                        itemsFutureNotifier.value = null;
                        _controller.text = '';
                      },
                     child: ...
                   );
                });
});

As you can see I now have a counter notifier that will actually rebuild the ValueListenableBuilder and will make the FutureBuilder fetch the data

Jose Georges
  • 883
  • 8
  • 16
  • Just curious. Can `itemsFuture` and `getItemsFuture` be a same function and use different parameter like **bool forceRefetch**. Second, will `itemsFuture` be null onCreate or it just use `null` to detect if the page need to be refresh? – yellowgray Dec 23 '20 at 15:43
  • itemsFuture _could_ be null onCreate, and `itemsFuture` is the result of `getItemsFuture` – Jose Georges Dec 23 '20 at 16:36
  • Is it necessary the `itemsFuture` be a future data? Can you show some code that describe the relation between `itemsFuture` and `getItemsFuture`? I think it should be a better way than setting the Future data to null if it is not necessary. – yellowgray Dec 24 '20 at 06:13
  • I've edited the question to add the explanation – Jose Georges Dec 24 '20 at 15:40

1 Answers1

1

I think itemsFuture is not necessary to set to null (because it can be a initial statement inside useState).

@override
Widget build(BuildContext context) {
  final fetchData = useState(itemsFuture ?? getItemsFuture());

  return Scaffold(
    body: FutureBuilder(
      future: fetchData.value,
      builder: (context, snapshot) {
        return RefreshIndicator(
          onRefresh: () async {
            fetchData.value = getItemsFuture();
          },
          child: ...
        );
      },
    ),
  );
}
yellowgray
  • 4,006
  • 6
  • 28