bihire boris
bihire boris

Reputation: 1662

how to dispose/kill a page in a PageView in flutter

I have a PageView with 2 pages, I have a custom stack of navigator for each of both pages.

At the initial start(let's say I star on page 1), page 2 is built when I start scrolling or jump to it (as normal).

thought when I am on page 2 and go back to page 1 and vice-versa, the pages are kept alive and I would like to dispose/kill them to force a rebuild..at least on page 2.

I tried wantKeepAlive = false but doesn't work.

can someone share a way to achieve that.

Upvotes: 1

Views: 4547

Answers (1)

Baker
Baker

Reputation: 27990

Rebuild on Swipe

If the goal is to tell our widgets to rebuild when pages are swiped back/forth, we can use the onPageChanged callback of PageView.builder.1

onPageChanged: will run the function we give it each time a page is viewed.

Linking onPageChanged to Rebuild Pages

There's no built-in connection between onPageChanged and rebuilding of pages in a PageView.

onPageChanged just runs any arbitrary function we give it, nothing more.

If we wish to rebuild pages with onPageChanged we need to create that linkage ourselves.

Link with ValueNotifier/ValueListenableBuilder

In this example, we're using ValueNotifier (the buildCount variable) to notify the ValueListenableBuilder to rebuild whenever buildCount changes. (See Flutter Docs for info.)

Each time we swipe pages the buildCount will increment and the ValueListenableBuilder in our pages will rebuild, displaying the new total:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

class PageViewRebuildPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('PageView Stream Rebuild'),
      ),
      body: PageViewRebuildExample()
    );
  }
}

class PageViewRebuildExample extends StatelessWidget {
  final List<String> contents = ['One', 'Two', 'Three'];
  final ValueNotifier<int> buildCount = ValueNotifier<int>(0);

  @override
  Widget build(BuildContext context) {
    print('Example built');
    return Center(
      child: PageView.builder(
        itemCount: contents.length,
        onPageChanged: (page) {
          print('PageChanged $page');
          buildCount.value++;
        },
        itemBuilder: (context, index) {
          return Container(
            alignment: Alignment.center,
            margin: EdgeInsets.symmetric(vertical: 40, horizontal: 20),
            color: Colors.lightBlueAccent,
            child: ValueListenableBuilder(
              valueListenable: buildCount,
                    // bldCnt here ↓↓ is buildCount.value
              builder: (context, bldCnt, child) =>
                  // ↓ YOUR STUFF GOES HERE ↓
                  Text('Page ${contents[index]} : $bldCnt',
                      style: TextStyle(fontSize: 30)),
            ),
          );
        },
      ),
    );
  }
}

To adapt this example for your purposes, you'd replace the builder: part of ValueListenableBuilder with your Widget. It will be rebuilt on each swipe.


Addendum

If you want to know the precise position of a page during a swipe gesture we can provide a PageController to PageView, attach a listener to that controller, and listen to the controller.page output:

class PageViewRebuildExample extends StatefulWidget {
  @override
  _PageViewRebuildExampleState createState() => _PageViewRebuildExampleState();
}

// Switched to StatefulWidget so we can add listener & dispose
class _PageViewRebuildExampleState extends State<PageViewRebuildExample> {
  final List<String> contents = ['One', 'Two', 'Three'];
  final ValueNotifier<int> buildCount = ValueNotifier<int>(0);
  final PageController controller = PageController(); // ← our new controller

  @override
  void initState() {
    super.initState();
    controller.addListener(() { // ← add a listener in onInit
      print('Precise page position ${controller.page}');
    });
  }
  
  @override
  void dispose() {
    controller.dispose(); // don't forget to dispose
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    print('Example built');

    return Center(
      child: PageView.builder(
        itemCount: contents.length,
        controller: controller, // ← PageController provided here
        onPageChanged: (page) {
          print('PageChanged $page');
          buildCount.value++;
        },

The output from our example listener above is (snipped to shorten):

I/flutter (14528): Precise page position 0.001861572265625
I/flutter (14528): Precise page position 0.006500244140625
I/flutter (14528): Precise page position 0.01019287109375
I/flutter (14528): Precise page position 0.012054443359375
... <snip>
I/flutter (14528): Precise page position 0.45981356897799835
I/flutter (14528): Precise page position 0.5179874247450384
I/flutter (14528): PageChanged 1
I/flutter (14528): Precise page position 0.5743095749455654
I/flutter (14528): Precise page position 0.6269737660740682
... <snip>
I/flutter (14528): Precise page position 0.9986377335325707
I/flutter (14528): Precise page position 0.9988375372072605
I/flutter (14528): Precise page position 0.9990080727700607
I/flutter (14528): Precise page position 1.0


1 There are likely many other ways to do this, this is just one method. For example, using Stream to send events from onPageChanged to a StreamBuilder inside itemBuilder is another.

Upvotes: 2

Related Questions