简体   繁体   中英

How to switch AppBar in the Flutter's Scaffold?

I have a situation where my AppBar in the Scaffold needs to be changed when I switch page on the BottomNavigationBar widget. One AppBar is a plain AppBar(title: Text('Saved')) and another is an AppBarTextField widget ( https://pub.dev/packages/appbar_textfield ). From this one, I would like to keep the text that was already entered before the switch. However with the below code, everytime I return to the relative page, the bar has been reset:

import 'package:flutter/material.dart';
import 'package:gifs/screens/main/search.dart';
import 'package:gifs/screens/main/saved.dart';
import 'package:gifs/screens/main/search_appbar.dart';

import '../../models/search_result.dart';

class Navigation extends StatefulWidget {
  Navigation({required Key key}) : super(key: key);

  final SearchResult _searchResult = SearchResult();

  late List<PreferredSizeWidget> _appBars = [
    SearchAppBar(key: GlobalKey(debugLabel: 'search_appbar_ui'), searchResult: _searchResult),
    AppBar(title: Text('Saved'))
  ];

  late List<Widget> _pages = [
    Search(key: GlobalKey(debugLabel: 'search_ui'), searchResult: _searchResult),
    Saved(key: GlobalKey(debugLabel: 'saved_ui')),
  ];

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

class _NavigationState extends State<Navigation> {

  int _currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: widget._appBars[_currentIndex],
      body: IndexedStack(
        index: _currentIndex,
        children: widget._pages,
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
          BottomNavigationBarItem(icon: Icon(Icons.favorite), label: 'Saved'),
        ],
        onTap: (index) => {
          setState(() {
            _currentIndex = index;
          })
        }
      ),
    );
  }
}

This is the SearchBar code:

import 'package:flutter/material.dart';
import 'package:gifs/models/search_result.dart';
import 'package:appbar_textfield/appbar_textfield.dart';


class SearchAppBar extends StatefulWidget with PreferredSizeWidget {

  final SearchResult searchResult;

  const SearchAppBar({required Key key, required this.searchResult}) : super(key: key);

  @override
  _SearchAppBarState createState() => _SearchAppBarState();

  @override
  Size get preferredSize => Size.fromHeight(kToolbarHeight);
}

class _SearchAppBarState extends State<SearchAppBar> {

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

    @override
    Widget build(BuildContext context) {
      return AppBarTextField(
        title: Text("Enter a search term"),
        onBackPressed: _onRestoreAllData,
        onClearPressed: _onRestoreAllData,
        onChanged: _onSearchChanged,
      );
    }

  void _onSearchChanged(String value) {
    widget.searchResult.searchTerm(value);
  }

  void _onRestoreAllData() {
    _onSearchChanged('');
  }

}

As you can see I switch the _appBars based on the index, but the text in the bar disappears after every single switch. Is there a way to keep its current status?

Try this: I wasn't able to run your own code but I tested out my solution in a starter flutter app and it worked (code provided at the very end)

This is your modified Navigation widget code:

import 'package:flutter/material.dart';
import 'package:gifs/screens/main/search.dart';
import 'package:gifs/screens/main/saved.dart';
import 'package:gifs/screens/main/search_appbar.dart';

import '../../models/search_result.dart';

class Navigation extends StatefulWidget {
  Navigation({required Key key}) : super(key: key);

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

class _NavigationState extends State<Navigation> {

  int _currentIndex = 0;
  
  final SearchResult _searchResult = SearchResult();

  List<PreferredSizeWidget> get _appBars => [
    SearchAppBar(
      key: GlobalKey(debugLabel: 'search_appbar_ui'), 
      searchResult: _searchResult,
      setSearchResult: (String value) {
        //set your string value of the SearchResult class
        setState(() => _searchResult.term = value);
      }
    ),
    AppBar(title: Text('Saved'))
  ];

  List<Widget> get _pages => [
    Search(key: GlobalKey(debugLabel: 'search_ui'), searchResult: _searchResult),
    Saved(key: GlobalKey(debugLabel: 'saved_ui')),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: widget._appBars[_currentIndex],
      body: IndexedStack(
        index: _currentIndex,
        children: widget._pages,
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
          BottomNavigationBarItem(icon: Icon(Icons.favorite), label: 'Saved'),
        ],
        onTap: (index) => {
          setState(() {
            _currentIndex = index;
          })
        }
      ),
    );
  }
}

Notes about what I did:

  • I moved your _appBars and _pages and _searchResult variables into your state class and removed late initialization and made them getters.
  • I ran a setState with the setSearchResult function to preserve the value of the field input (see next code snippet notes)

And this is your modified SearchAppBar widget code:

import 'package:flutter/material.dart';
import 'package:gifs/models/search_result.dart';
import 'package:appbar_textfield/appbar_textfield.dart';


class SearchAppBar extends StatefulWidget with PreferredSizeWidget {

  final SearchResult searchResult;
  final ValueChanged<String> setSearchResult;

  const SearchAppBar({required Key key, required this.searchResult, required this.setSearchResult}) : super(key: key);

  @override
  _SearchAppBarState createState() => _SearchAppBarState();

  @override
  Size get preferredSize => Size.fromHeight(kToolbarHeight);
}

class _SearchAppBarState extends State<SearchAppBar> {

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

    @override
    Widget build(BuildContext context) {
      return AppBarTextField(
        title: Text("Enter a search term"),
        onBackPressed: _onRestoreAllData,
        onClearPressed: _onRestoreAllData,
        onChanged: _onSearchChanged,
        // Give your string value of the SearchResult class
        // as an initial value to your text field.
        // If this was a regular TextFormField() widget you would
        // have been able to use the initialValue param, but I'm not
        // sure if there's a better alternative than using the 
        // controller for this package
        controller: TextEditingController()..text = widget.searchResult.term,
      );
    }

  void _onSearchChanged(String value) {
    widget.setSearchResult(value);
  }

  void _onRestoreAllData() {
    _onSearchChanged('');
  }

}

Notes about what I did:

  • I called the widget.setSearchResult function in the AppBarTextField 's onChanged param to update the value in parent widget
  • VERY IMPORTANT: I assigned the widget.searchResult 's string value as an initial value to the text field so that when you revisit the page it is assigned again to the text field and not lost.

And this is my code in a sandbox app if you want to try it out: (put it in a dartpad and run it and you should see it working)

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: _title,
      home: MyStatefulWidget(),
    );
  }
}

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  String searchResult = '';

  List<PreferredSizeWidget> get _appBars => [
        SearchAppBar(
          setSearchResult: (String value) =>
              setState(() => searchResult = value),
          searchResult: searchResult,
        ),
        AppBar(title: const Text('Saved')),
      ];

  int _selectedIndex = 0;
  static const TextStyle optionStyle =
      TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
  static const List<Widget> _widgetOptions = <Widget>[
    Text(
      'Index 0: Home',
      style: optionStyle,
    ),
    Text(
      'Index 1: Business',
      style: optionStyle,
    ),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _appBars[_selectedIndex],
      body: Center(
        child: _widgetOptions.elementAt(_selectedIndex),
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
            backgroundColor: Colors.red,
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            label: 'Business',
            backgroundColor: Colors.green,
          ),
        ],
        currentIndex: _selectedIndex,
        selectedItemColor: Colors.amber[800],
        onTap: _onItemTapped,
      ),
    );
  }
}

class SearchAppBar extends StatefulWidget with PreferredSizeWidget {
  final String searchResult;
  final ValueChanged<String> setSearchResult;

  const SearchAppBar({required this.setSearchResult, this.searchResult = ''});

  @override
  _SearchAppBarState createState() => _SearchAppBarState();

  @override
  Size get preferredSize => const Size.fromHeight(56);
}

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

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      onChanged: _onSearchChanged,
      controller: TextEditingController()..text = widget.searchResult,
//       initialValue: widget.searchResult,
    );
  }

  void _onSearchChanged(String value) {
    widget.setSearchResult(value);
  }
}

Hope this helps!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM