3

I want to hide floating actionbutton in the bottom of listview which is built using FutureBuilder, I've used ScrollerController inside listview to detect when user scrolls forward and hides the actionbuttons if user scrolls forwrad then called setState but whenever I call it FutureBuilder future will run repeatedly which is a async sqlite get query. I used AsynMemoizer but that didn't help too. here is my code sample:

class _MyHomePageState extends State<MyHomePage> {
  bool show = true;
  ScrollController _controller = ScrollController();
  final AsyncMemoizer _memoizer = AsyncMemoizer();

  @override
  void initState() {
    super.initState();
    _controller.addListener(listener);
  }

  void listener() {
    if (_controller.position.userScrollDirection == ScrollDirection.forward) {
      show = true;
    } else if (_controller.position.userScrollDirection ==
        ScrollDirection.reverse) {
      show = false;
    }
    setState(() {});
  }

  int _counter = 0;
  int id = 0;
  Future<List<Dog>> _getDogs() async {
    // return this._memoizer.(() async {
    //   return await getDogs();
    // });
  }

  void _insertDog() async {
    id++;
    var fido = Dog(id: id, name: "fido$id", age: id * 3);
    await insertDog(fido);
    setState(() {});
  }

  void _updateDog(Dog fido) async {
    fido = Dog(
      id: fido.id,
      name: fido.name,
      age: fido.age + 7,
    );
    await updateDog(fido);
    setState(() {});
  }

  void _deleteDog(int id) async {
    await deleteDog(id);
    setState(() {});
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Sqflite Demo"),
        ),
        body: Container(
          child: FutureBuilder(
            future: getDogs(),
            builder: (BuildContext context, AsyncSnapshot snapshot) {
              if (snapshot.connectionState == ConnectionState.done) {
                return ListView.builder(
                  controller: _controller,
                  itemCount: snapshot.data.length,
                  itemBuilder: (BuildContext context, int index) {
                    return ListTile(
                      title: Text(snapshot.data[index].name),
                      subtitle: Text("Age: ${snapshot.data[index].age}"),
                      onTap: () => _updateDog(snapshot.data[index]),
                      onLongPress: () => _deleteDog(snapshot.data[index].id),
                    );
                  },
                );
              } else {
                return Container(
                    child: Center(
                  child: Text("Loading..."),
                ));
              }
            },
          ),
        ),
        floatingActionButton: Visibility(
          visible: show,
          child: Stack(
            children: <Widget>[
              Padding(
                padding: EdgeInsets.only(left: 30),
                child: Align(
                  alignment: Alignment.bottomLeft,
                  child: FloatingActionButton(
                    onPressed: () {
                      setState(() {});
                    },
                    child: Icon(Icons.refresh),
                  ),
                ),
              ),
              Align(
                alignment: Alignment.bottomRight,
                child: FloatingActionButton(
                  onPressed: _insertDog,
                  tooltip: 'Increment',
                  child: Icon(Icons.add),
                ),
              ),
            ],
          ),
        ));
  }
}
monajafi
  • 53
  • 1
  • 8

2 Answers2

10

Call getDogs() in the initState, saving the Future instance in the State. Then use that Future variable in the FutureBuilder. That way it will not get called on each build if it already has been resolved.

Related with extra information: https://stackoverflow.com/a/52249579/5617722

Martyns
  • 3,605
  • 22
  • 33
1

Thanks Martyns for a quick and smart solution,one point is that whenever I want to update listview I had to reassign a getDogs() to future state instance variable to force the FutureBuilder to run. Here is the correct source code:

class _MyHomePageState extends State<MyHomePage> {
  bool show = true;
  ScrollController _controller = ScrollController();
  Future<List<Dog>> future;
  @override
  void initState() {
    super.initState();
    future = getDogs();
    _controller.addListener(listener);
  }

  void listener() {
    if (_controller.position.userScrollDirection == ScrollDirection.forward) {
      show = true;
    } else if (_controller.position.userScrollDirection ==
        ScrollDirection.reverse) {
      show = false;
    }
    setState(() {});
  }

  int _counter = 0;
  int id = 0;
  void _insertDog() async {
    id++;
    var fido = Dog(id: id, name: "fido$id", age: id * 3);
    await insertDog(fido);
    setState(() {future = getDogs();});
  }

  void _updateDog(Dog fido) async {
    fido = Dog(
      id: fido.id,
      name: fido.name,
      age: fido.age + 7,
    );
    await updateDog(fido);
    setState(() {future = getDogs();});
  }

  void _deleteDog(int id) async {
    await deleteDog(id);
    setState(() {future = getDogs();});
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Sqflite Demo"),
        ),
        body: Container(
          child: FutureBuilder(
            future: future,
            builder: (BuildContext context, AsyncSnapshot snapshot) {
              if (snapshot.connectionState == ConnectionState.done) {
                return ListView.builder(
                  controller: _controller,
                  itemCount: snapshot.data.length,
                  itemBuilder: (BuildContext context, int index) {
                    return ListTile(
                      title: Text(snapshot.data[index].name),
                      subtitle: Text("Age: ${snapshot.data[index].age}"),
                      onTap: () => _updateDog(snapshot.data[index]),
                      onLongPress: () => _deleteDog(snapshot.data[index].id),
                    );
                  },
                );
              } else {
                return Container(
                    child: Center(
                  child: Text("Loading..."),
                ));
              }
            },
          ),
        ),
        floatingActionButton: Visibility(
          visible: show,
          child: Stack(
            children: <Widget>[
              Padding(
                padding: EdgeInsets.only(left: 30),
                child: Align(
                  alignment: Alignment.bottomLeft,
                  child: FloatingActionButton(
                    onPressed: () {
                      setState(() {future = getDogs();});
                    },
                    child: Icon(Icons.refresh),
                  ),
                ),
              ),
              Align(
                alignment: Alignment.bottomRight,
                child: FloatingActionButton(
                  onPressed: _insertDog,
                  tooltip: 'Increment',
                  child: Icon(Icons.add),
                ),
              ),
            ],
          ),
        ));
  }
monajafi
  • 53
  • 1
  • 8