I am issue a weird situation. I have the following code that display the tabs and their contents:
Scaffold(
appBar: AppBar(
title: Text('Title'),
actions: [
IconButton(
icon: Icon(Icons.add),
onPressed: () => _onTabAdd(_tabController.index),
),
IconButton(
icon: Icon(Icons.delete),
onPressed: () => _onTabRemoved(_tabController.index),
),
IconButton(
icon: Icon(Icons.edit),
onPressed: () => _onTabEdit(context),
),
],
bottom: PreferredSize(
preferredSize: Size.fromHeight(40.0),
child: Container(
color: Colors.blue,
child: TabBar(
controller: _tabController,
tabs: tabTitles
.map(
(title) => Tab(text: title),
)
.toList(),
labelColor: Colors.yellow,
unselectedLabelColor: Colors.white,
indicatorSize: TabBarIndicatorSize.label,
indicatorPadding: EdgeInsets.all(5.0),
indicatorColor: Colors.red,
),
),
),
),
body: TabBarView(
controller: _tabController,
children: tabContents.map((e) => e).toList(),
),
You can see that I have the option to add or delete Tabs using the buttons, and it seems to work fine using the following functions:
void _onTabRemoved(int index) {
setState(() {
tabTitles.removeAt(index);
tabContents.removeAt(index);
_tabController = TabController(length: tabTitles.length, vsync: this);
});
}
void _onTabAdd(int index) {
setState(() {
tabTitles.add("Tab ${tabTitles.length}");
tabContents.add(GenesTable(this));
_tabController = TabController(length: tabTitles.length, vsync: this);
});
}
The problem is that when I move between tabs I lose the state of GenesTable component (that store the table's content), meaning that when I move between tabs The table that been display is empty.
You can go through the answer given here
https://stackoverflow.com/a/51225319/13927133
In case you want to keep the state of your screen in your TabBarView, you can use the mixin class called AutomaticKeepAliveClientMixin in your State class.
And after that override the wantKeepAlive method and return true.
#diegoveloper also posted a medium article for this
https://medium.com/#diegoveloper/flutter-persistent-tab-bars-a26220d322bc
Related
I use a default Tabbar. I have two tab .When I change tabview by clicking, onTab method call finely. But when I change tabview by swiping or scrolling, how I can call onTab method?. How I can listen my onTab changing value when I change my tabview by swiping or scrolling? I need change tabIndex value in controller when I change tabView by swiping or scroling.
UI Part here
#override
Widget build(BuildContext context) {
return DefaultTabController(
initialIndex: 0,
length: 2,
child: Scaffold(
appBar: AppBar(
backgroundColor: AllColors.deepPurple,
leading: InkWell(
onTap: () => Get.back(),
child: Icon(
Icons.arrow_back,
color: AllColors.whiteColor,
),
),
elevation: 0.0,
title: Text(
"Categories",
style: AllStyles.titleTextStyle,
),
actions: [
InkWell(
child: Padding(
padding: const EdgeInsets.only(right: 12.0),
child: Icon(Icons.add),
),
onTap: () {
},
)
],
bottom: TabBar(
controller: categoriesController.tabController,
onTap: (value) {
categoriesController.changeTabValue(value);
print("Value " + value.toString());
},
isScrollable: false,
indicatorColor: AllColors.whiteColor,
indicatorSize: TabBarIndicatorSize.label,
tabs: [Tab(text: "Income"), Tab(text: "Expense")],
),
),
body: TabBarView(
children: [
IncoomeTabCategories(),
ExpenseTabCategories()
],
),
),
);
}
Controller part here:
class CategoriesController extends GetxController with GetSingleTickerProviderStateMixin {
TabController? tabController;
int tabIndex=0;
#override
void onInit() {
super.onInit();
tabController = TabController(length: 2, vsync: this,initialIndex: 0)
}
#override
void dispose() {
super.dispose();
tabController!.dispose();
}
void changeTabValue(int index){
tabIndex=index;
update();
}
}
So in here i want to change some condition if my tabview switch to the second tab but i don't know how to get the tabbar index, already try this and that. Im hoping some solution without statefull, Im using GetX thanks.
im planning to change the extendBody: true, in my main page to false when the tab switch to the second tab i had the logic for that hopefully but the only problem is the index :(.
My tabs :
List<Tab> myTabs = [
Tab(
text: 'Following',
),
Tab(
text: 'Trending',
),
Tab(
text: 'Search',
),
];
DefaultController code :
DefaultTabController(
length: myTabs.length,
child: Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: bgColor,
// APPBAR
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
toolbarHeight: 60,
// BOTTOM
bottom: PreferredSize(
preferredSize: const Size.fromHeight(0),
child: Align(
alignment: Alignment.centerLeft,
child: TabBar(
isScrollable: true,
labelPadding: EdgeInsets.only(left: 20),
labelColor: Colors.white,
labelStyle: poppins.copyWith(
fontSize: 15,
fontWeight: bold,
),
unselectedLabelColor: Color(0xff585861),
indicatorColor: Colors.white.withOpacity(0),
indicatorSize: TabBarIndicatorSize.label,
// TABS
tabs: myTabs,
),
),
),
),
body: TabBarView(
children: [
FollowingTab(),
TrendingTab(),
search(),
],
),
),
);
Use TabBar with TabController and you can find current index while switching to next tab
#override
void initState() {
super.initState();
_controller = TabController(length: 6, vsync: this);
_controller!.addListener(() {
print(_controller!.index);
});
}
your build method be like:
#override
Widget build(BuildContext context) {
return Scaffold(
bottom:TabBar(
controller: _controller,
tabs:[
//your tabs will be here
]
),
body:TabBarView(
controller: _controller,
children: [
//your tabbarview will be here
]
),
);
}
Here i just code of Tabbar with using Getx and Stateless Widget.
CheckOut my code and if you find solution then give up me. Thanks in advance
class TabDemo extends StatelessWidget {
TabDemo({Key? key}) : super(key: key);
final DemoController demoController = Get.put(DemoController());
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: const TabBar(
tabs: [
Tab(icon: Icon(Icons.flight)),
Tab(icon: Icon(Icons.directions_transit)),
Tab(icon: Icon(Icons.directions_car)),
],
),
title: const Text('Tabs Demo'),
),
body: TabBarView(
controller: demoController.controller?.value,
children: const [
Icon(Icons.flight, size: 350),
Icon(Icons.directions_transit, size: 350),
Icon(Icons.directions_car, size: 350),
],
),
),
);
}
}
You need to create controller class to define controller and initmethod
class DemoController extends GetxController with SingleGetTickerProviderMixin {
Rx<TabController>? controller;
#override
void onInit() {
// TODO: implement onInit
controller?.value = TabController(length: 6, vsync: this);
controller?.value.addListener(() {
print(controller?.value.index);
});
super.onInit();
}
}
I have a menu page which has tabA and tabB. The appBar has a search button.
menu.dart
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Item List'),
actions: [
_currentIndex == 0
? Row(
children: [
IconButton(
onPressed: () {
Navigator.pushNamed(context, SearchScreen.ROUTE,
arguments: list);
},
icon: Icon(
Icons.search,
color: Colors.white,
)),
IconButton(
icon: Icon(
Icons.tune,
color: Colors.white,
),
onPressed: () {
...
}
}
});
},
)
],
)
: Container()
],
bottom: TabBar(
unselectedLabelColor: Colors.white,
labelColor: Colors.white,
isScrollable: false,
tabs: <Widget>[
Tab(
text: "Tab A",
),
Tab(
text: "Tab B",
),
],
controller: _tabController,
indicatorColor: Colors.white,
indicatorSize: TabBarIndicatorSize.tab,
),
bottomOpacity: 1,
),
body: TabBarView(
controller: _tabController,
children: <Widget>[
ShowTabA(),
ShowTabB()
],
),
);
}
}
In tabA, it has a listView. When I tap on one of the listview item, it will navigate to EditPage. When return from EditPage, I will refresh the data in tabA. All the list item will be updated.
tabA.page
initState(){
_bloc.getItemList();
}
Navigator.pushNamed(EditPage.ROUTE,context).then((onValue){
_bloc.getItemList();
});
My problem here:
When I click the search icon in menu page , it will navigate to SearchScreen page.Once user click the list item in SearchScreen, it will navigate to EditPage.
When back from EditPage, I want the list in TabA page refreshed but it can't because I navigate using pushReplacementNamed in menu page . How to achieve that ?
SearchScreen.dart
Navigator.pushReplacementNamed(context);
Updated answer on Modified Question:
Since you are using pushReplacement your await won't work. You can use the following approach to make it work:
First you have to define your routes:
routes: <String, WidgetBuilder> {
'/': (BuildContext context) => // Your MainPage,
'/menu': (BuildContext context) => MenuPage(),
'/search': (BuildContext context) => SearchPage(),
'/edit': (BuildContext context) => EditPage(),
}
Navigation from Menu to Search:
void goToSearch() async {
await Navigator.pushNamed(context, "/search", arguments: list);
_bloc.getItemList().then((onValue){
setState(() {
list = onValue;
});
});
}
Use pushNamed instead of pushReplacementNamed when navigating from Search to Edit:
Navigator.of(context).pushNamed("/edit");
Important step When you have to go back to Menu from Edit, you've to use popUntil method:
Navigator.popUntil(context, ModalRoute.withName('/menu'));
I have made a flutter project in which I am facing issue with two pages.
Plugin used for two pages- https://pub.dev/packages/flutter_inappwebview
One Page that is "Single App page" render a Webpage & WillPopScope is working here absolutely fine.
Another Page that is "Compare App Page" render a different Webpages into different tabs & here WillPopScope is only working for 1st tab and not working for the rest of tab.
I want to implement WillPopScope for each tab so that each tab have its own history and when a person present on a particular tab & hitting back button(I want to do this inbuilt back button & not via a created back button) takes him to back in history.
Note- A common widget is used in both Single & Compare App as children.
Below are the Main code
class NewCompareApp extends StatefulWidget {
#override
_NewCompareAppState createState() => _NewCompareAppState();
}
class _NewCompareAppState extends State<NewCompareApp> {
List apps;
#override
void initState() {
super.initState();
apps = getCompareApps();
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: apps.length,
child: Scaffold(
appBar: AppBar(
titleSpacing: 0,
title: Card(
elevation: 10,
child: Container(
height: 35,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(8.0)),
),
padding: EdgeInsets.only(left: 5),
child: TextField(
autofocus: false,
cursorColor: Colors.grey,
decoration: InputDecoration(
hintText: 'Search', border: InputBorder.none),
),
),
),
bottom: TabBar(
indicatorWeight: 1,
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
indicatorColor: Colors.white,
isScrollable: true,
tabs: apps
.map((ca) => Tab(
text: ca.name,
))
.toList(),
),
),
resizeToAvoidBottomInset: false,
body: TabBarView(
physics: NeverScrollableScrollPhysics(),
children: apps
.map((ca) => WebApp(
url: ca.url,
forWidget: 'cmp',
))
.toList(),
)),
);
}
}
class WebApp extends StatefulWidget {
final String url;
final String forWidget;
WebApp({Key key, #required this.url, #required this.forWidget})
: super(key: key);
#override
_WebAppState createState() => _WebAppState();
}
class _WebAppState extends State<WebApp>
with AutomaticKeepAliveClientMixin<WebApp> {
#override
bool get wantKeepAlive => true;
var currentUrl = '';
InAppWebViewController controller;
Future<bool> _handleBack(context) async {
var status = await controller.canGoBack();
if (status) {
controller.goBack();
} else {
getExitDialog(context, extra: {
"in_app": true,
});
}
return false;
}
#override
Widget build(BuildContext context) {
Widget mainWidget = Column(
children: <Widget>[
Expanded(
child: WillPopScope(
onWillPop: () => _handleBack(context),
child: InAppWebView(
initialUrl: widget.url,
onWebViewCreated: (InAppWebViewController webViewController) {
controller = webViewController;
},
onLoadStart: (InAppWebViewController controller, String url) {
this.currentUrl = url;
},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
horizontalScrollBarEnabled: false,
verticalScrollBarEnabled: false),
),
),
),
),
Container(
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
FlatButton(
padding: EdgeInsets.zero,
child: Icon(Icons.arrow_back),
onPressed: () => _handleBack(context),
),
FlatButton(
padding: EdgeInsets.zero,
child: Icon(Icons.refresh),
onPressed: () {
if (controller != null) {
controller.reload();
}
},
),
FlatButton(
padding: EdgeInsets.zero,
child: Icon(Icons.arrow_forward),
onPressed: () {
if (controller != null) {
controller.goForward();
}
},
),
FlatButton(
padding: EdgeInsets.zero,
child: Icon(Icons.share),
onPressed: null,
),
],
),
),
],
);
return widget.forWidget == 'single_app'
? Scaffold(body: SafeArea(top: true, child: mainWidget))
: mainWidget;
}
}
Code in detail- https://gist.github.com/ycv005/13dec1df2b57535271eb346e132c6775
Thanks in advance.
After struggling I found below solution where a global controller that keep changing on tab change and plus have a local controller as well to handle other stuff.
In bottom, selecting global controller and TabBarView(wrapping with WillPopScope)
bottom: TabBar(
onTap: (int index) async {
currentIndex = index;
print('here is index- $index');
if (tabWebControllerMap.containsKey(currentIndex)) {
globalController = tabWebControllerMap[currentIndex];
final hereUrl = await globalController.getUrl();
print('here url- $hereUrl');
}
},
controller: _tabController,
indicatorWeight: 1,
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
indicatorColor: Colors.white,
isScrollable: true,
tabs: apps
.map((ca) => Tab(
child: Text(
ca.name,
style: TextStyle(fontSize: 12),
),
))
.toList(),
),
),
In my app, I have a search page and when I click on the search text field bottom navigation bar also moves up with the keyboard where it supposed to be hidden under the keyboard. Because while the keyboard is showing I can navigate to other pages which is undesirable behavior.
The Code:
class _AppHomeViewState extends State<AppHomeView>
with TickerProviderStateMixin {
TabController tabController;
#override
void initState() {
super.initState();
tabController = TabController(length: 4, vsync: this, initialIndex: 0);
tabController.addListener(handleTabSelection);
}
#override
Widget build(BuildContext context) {
final scaffold = Scaffold(
body: SafeArea(child: _buildBody(context)),
bottomNavigationBar: Container(
height: 48,
decoration: BoxDecoration(
color: StyledColors.BACKGROUND_COLOR,
boxShadow: [
BoxShadow(
color: StyledColors.FORGROUND_COLOR.withOpacity(0.16),
blurRadius: 12,
offset: Offset(0, 0),
),
],
),
child: SafeArea(
child: _buildTabBar(context),
),
),
);
}
Widget _buildBody(BuildContext context) {
return TabBarView(
physics: NeverScrollableScrollPhysics(),
controller: tabController,
children: <Widget>[
HomeView(),
SearchView(),
OrdersView(),
ProfileView(),
],
);
}
Widget _buildTabBar(BuildContext context) {
return TabBar(
controller: tabController,
tabs: <Widget>[
Tab(
icon: Icon(
Icons.store,
size: 28,
),
),
Tab(
icon: Icon(
Icons.search,
size: 28,
),
),
Tab(
icon: Icon(
Icons.receipt,
size: 28,
),
),
Tab(
icon: Icon(
Icons.person,
size: 28,
),
)
],
indicatorColor: Colors.transparent,
unselectedLabelColor: StyledColors.MEDIUM_GREY,
labelColor: StyledColors.PRIMARY_COLOR,
);
}
void handleTabSelection() {
setState(() {});
}
}
What is supposed to behave is when I click on the search, the bottom navigation bar should stay behind the keyboard and not come up with the keyboard?
set the resizeToAvoidBottomInset: false, in the Scaffold widget
If you have nested Scaffold, check that your root Scaffold also has resizeToAvoidBottomInset: false set.
I just came across the same problem where my bottomNavbar is moving up with the keyboard when the keyboard is enabled. I solved it by checking if the keyboard is open or not. If it is open, just hide the disable the bottomNavbar and when it is closed, it's time to enable the navbar..
Here is how it looks..
First create a boolean variable which checks if the keyboard is open or not..
bool isKeyboardOpen = MediaQuery.of(context).viewInsets.bottom != 0.0;
Now do something like this to toggle your bottomNavbar
bottomNavigationBar: isKeyboardOpen
? null
: BottomAppBar();
This technique also works with the floating action bottom issue..