AJ-
AJ-

Reputation: 1783

Flutter - how to keep the state dynamically for Widgets inside Bottom Navigation Bar

I have 2 pages (Page A and Page B) that you can navigate from and to via a bottom navigation bar.

I have a special use can where:

Page A : must be "keptAlive" most of the time, only 1 scenario where it should rebuild

Page B: must be rebuilt every time we switch to it.

I got these behaviors working but one, the Dynamic keepAlive for Page A.

First of all, the code:

Main Page that holds the navigation:

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  int _currentIndex = 0;
  PageController? _pageController;
  List<Widget> _pages = [];

  @override
  void initState() {
    super.initState();

    _currentIndex = 0;
    _pages = [PageA(), PageB()];

    _pageController = PageController(initialPage: _currentIndex);
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text('example')),
      body: PageView(
      controller: _pageController,
      physics: NeverScrollableScrollPhysics(),
      children: _pages,
    ),
      bottomNavigationBar: AnimatedBottomNavigationBar.builder(
        itemCount: _iconList.length,
        activeIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
            _pageController!.jumpToPage(index);
            });
        },
      ),
    );
  }

Now this part works fine, in Page B I don't need to do anything special, as by default with PageView, everytime I switch to that Page, it will rebuild.

Now, for Page A, I want that most of the time it DOESN'T rebuild if navigating from Page B

And I achieve this with: AutomaticKeepAliveClientMixin and wantKeepAlive :

Page A widget:

class PageA extends StatefulWidget {
  @override
  _PageAState createState() => _PageAState();
}

class _PageAState extends State<PageA> with AutomaticKeepAliveClientMixin<PageA> {
 bool shouldKeepAlive = true;

  @override
  void initState() {
    super.initState();
  }

  @override
  bool get wantKeepAlive => shouldKeepAlive;

@override
  Widget build(BuildContext context) {
    super.build(context);
    /// ..... 

So this works great, the Page A is not being rebuilt, but I have a special case, where if in Page A, the user fills a textField with any value, and then switches to Page B, I want that if the user goes back to Page A, now it has been reset and rebuilt!

I tried different approaches, but I cannot change the value of wantKeepAlive once Page A has been built for the first time. I tried using updateKeepAlive() but it doesn't help, so does someone have an idea, how I can dynamically set wantKeepAlive on Page A, so that if I navigate to Page B under some special conditions, when coming back to Page A, it might or might not be reset.

Upvotes: 1

Views: 4703

Answers (1)

KuKu
KuKu

Reputation: 7502

Using 'updateKeepAlive' method in 'AutomaticKeepAliveClientMixin' class,
you can change keepAlive condition.

If user type some text at 'TextField' in the PageA,
set 'shouldKeepAlive' to false and call 'updateKeepAlive' method to update keep alive option.

I confirmed working well by pushing log message to build() method to confirm when build() is called.

-----Modification----
I don't know exactly why 'updateKeepAlive' is adapted after focusing is out from the TextField widget.
I changed some code to detach focus from TextField after keyboard is disappeared by using 'down arrow' or 'checkmark' button.
For this reason, I used one package to check whether keyboard is show or hide. https://pub.dev/packages/keyboard_visibility

enter image description here

import 'package:animated_bottom_navigation_bar/animated_bottom_navigation_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:keyboard_visibility/keyboard_visibility.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Example(),
    );
  }
}

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  int _currentIndex = 0;
  PageController _pageController;
  List<Widget> _pages = [];

  final iconList = <IconData>[
    Icons.brightness_5,
    Icons.brightness_4,
  ];

  @override
  void initState() {
    super.initState();

    _currentIndex = 0;
    _pages = [PageA(), PageB()];

    _pageController = PageController(initialPage: _currentIndex);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('example')),
      body: PageView(
        controller: _pageController,
        physics: NeverScrollableScrollPhysics(),
        children: _pages,
      ),
      bottomNavigationBar: AnimatedBottomNavigationBar.builder(
        itemCount: 2,
        activeIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
            _pageController.jumpToPage(index);
          });
        },
        tabBuilder: (int index, bool isActive) {
          final color = isActive ? Color(0XFFFFA400) : Colors.grey;
          return Column(
            mainAxisSize: MainAxisSize.min,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Icon(
                iconList[index],
                size: 24,
                color: color,
              ),
              const SizedBox(height: 4),
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 8),
                child: Text(
                  "Page $index",
                  maxLines: 1,
                  style: TextStyle(color: color),
                ),
              )
            ],
          );
        },
      ),
    );
  }
}

class PageA extends StatefulWidget {
  @override
  _PageAState createState() => _PageAState();
}

class _PageAState extends State<PageA>
    with AutomaticKeepAliveClientMixin<PageA> {
  bool shouldKeepAlive = true;
  String input;
  
  @override
  void initState() {
    super.initState();
    KeyboardVisibilityNotification().addNewListener(
      onChange: (bool visible) {
        if (!visible) {
          FocusManager.instance.primaryFocus.unfocus();
          print('keyboard disappeared');
          if (!shouldKeepAlive && input.isEmpty) {
            shouldKeepAlive = true;
            updateKeepAlive();
          } else if (shouldKeepAlive && input.isNotEmpty) {
            shouldKeepAlive = false;
            updateKeepAlive();
          }
        }
      },
    );
  }

  @override
  bool get wantKeepAlive => shouldKeepAlive;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    print('PageA is builded');

    return TextField(
      onSubmitted: (String value) {
        print('onSumitted: $value');
      },
      onChanged: (String value) {
        input = value;
        print('onChanged: $value');
      },
    );
  }
}

class PageB extends StatefulWidget {
  @override
  _PageBState createState() => _PageBState();
}

class _PageBState extends State<PageB> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    print('PageB is builded');
    return TextField();
  }
}

Upvotes: 2

Related Questions