I am using Google Maps for Flutter widget.
In my app map is displayed via one of the tabs of BottomNavigationBar.
And I have the following problem:
user is on Map's tab
user changes tab (by tapping on another one)
[PROBLEM] when user returns on Map's tab map redraws.
I would like to keep map as it is when user leaves Map's tab, so he can continue to work with it when he returns to it later on.
Tried to:
use PageStorage - without success.
make something like Singletone of Map's state - without success.
use AutomaticKeepAliveClientMixin (saw here), which looked promising, but still without success.
(I admit that I could have done something wrong)
Code of last attempt:
class MapScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => MapScreenState();
}
class MapScreenState extends State<MapScreen> with AutomaticKeepAliveClientMixin {
GoogleMapController mapController;
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
appBar: AppBar(
title: const Text("Map"),
),
body: GoogleMap(
onMapCreated: _onMapCreated,
)
);
}
void _onMapCreated(GoogleMapController controller) {
mapController = controller;
updateKeepAlive();
}
}
So, I just need a way to either keep MapScreen alive and unchanged, or to store its state somehow and restore it when user returns to MapScreen. Or something else which will solve the problem.
Use IndexedStack
For example:
Class _ExamplePageState extends State<ExamplePage> {
int _bottomNavIndex = 0;
final List<Widget> _children = [
WidgetOne(),
WidgetTwo(),
GoogleMap(),
]
#override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _bottomNavIndex,
children: _children,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _bottomNavIndex,
onTap: (index) {
if (_bottomNavIndex == index) return;
setState(() {
_bottomNavIndex = index;
});
}
items: [ ... ]
),
);
}
}
Widget always rebuild after you change from page to page, so, try this , use a variable for GoogleMap and reuse if it's different from null.
GoogleMap _map;
#override
Widget build(BuildContext context) {
if (_map == null){
_map = GoogleMap(
onMapCreated: _onMapCreated,
);
}
return Scaffold(
appBar: AppBar(
title: const Text("Map"),
),
body:_map,
);
}
I was just having this same problem and the mixin solved it. I think you've just missed enter the type of mixin at the end of its declaration.
class MapScreenState extends State<MapScreen> with AutomaticKeepAliveClientMixin<MapScreenState>
see this thread if anything.
AutomaticKeepAliveClientMixin
To
AutomaticKeepAliveClientMixin<MapScreenState>
Related
I am trying to call a custom widget that has a function as a parameter, however I got no clue what parameter I could assign to it. I have tested all the ideas I came up with but with no success. The idea for this, I have gained from a tutorial, however I have done some things differently, as I have different requirements.
This is my code:
import 'package:flutter/material.dart';
class NewTransaction extends StatelessWidget {
final Function addTx;
const NewTransaction({Key? key, required this.addTx}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text('I cant solve this problem');
}
}
========================================================================
import 'package:flutter/material.dart';
import 'package:function_parameter_problem/new_transaction.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
NewTransaction(
addTx:
addTx), // What parameter can/should I pass here? It is crucial for my project
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
Edit: Okay, but let's say that the NewTransaction() function returnes a scaffold with appbar, etc and I have to call it again, however in the following scenario:
_wykonajZapytanie() { //function called onPressed in the main window
Navigator.push(
context,
MaterialPageRoute(builder: (context) => NewTransaction()),
); /*I have to put a parameter here, but I just want to display another window, I don't know why, but I just can't get any ideas*/
}
IMPORTANT TO NOTICE In the original project, if it comes to the first example I have provided, I do not call NewTransaction(addTx...) in the main class, but in the UserTransaction class.
tldr I need to assemble an expandable list in the main class - which can be done the way below from what I know, but also I need to call this function in the the main class on the onPressed of a button to display all of the textfields, etc.
import 'package:flutter/material.dart';
import './new_transaction.dart';
import './transaction_list.dart';
import '../models/transaction.dart';
class UserTransaction extends StatefulWidget {
#override
_UserTransactionState createState() => _UserTransactionState();
}
class _UserTransactionState extends State<UserTransaction> {
final List<Transaction> _userTransactions = [
Transaction(
id: 1,
date: DateTime.now(),
numTel: 911911911,
// scoring: 'Link wygasł',
user: 'Polizei pau pau'),
Transaction(
id: 2,
date: DateTime.now(),
numTel: 911911911,
// scoring: 'Link wygasł',
user: 'Tha police')
];
void _addNewTransaction(int txNumTel, /* String txScoring,*/ String txUser) {
final newTx = Transaction(
numTel: txNumTel,
/*scoring: txScoring,*/
user: 'PLZ WORK',
date: DateTime.now(),
id: 3,
);
setState(() {
_userTransactions.add(newTx);
});
}
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[
NewTransaction(_addNewTransaction),
]);
}
}
import 'package:flutter/material.dart';
class NewTransaction extends StatelessWidget {
final VoidCallback function;
//if nothing returns
//else typedef CustomFunc = Function(int param); (replace int with your data type)
const NewTransaction({Key? key, required this.function}) : super(key: key);
#override
Widget build(BuildContext context) {
return InkWell(
child: Text('I cant solve this problem'),
onTap:function,
//if custom
//onTap: (){
// function(params);
//}
);
}
}
Call like below...
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
NewTransaction(
function: (){
//todo
},
//if custome function: (params){ todo },
)
],
),
),
If I understood your question correctly, this is the way you can pass a function with parameters:
import 'package:flutter/material.dart';
class NewTransaction extends StatelessWidget {
final Function() addTx;
const NewTransaction({Key? key, required this.addTx}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text('I cant solve this problem');
}
}
When passing this Function, make sure to pass it like this: NewTransaction(addTx: () => addTx());. If you pass it like this: NewTransaction(addTx: addTx()); the function gets called instantly.
When you sayFunction addTx it means any function can be passed.
If Function() addTx it means function with no parameter.
If Function(int) addTx it means function with one required positional parameter integer.
If void Function() addTx it means function with no parameter and return type should be void.
In flutter you can also use
VoidCallback which is basically void Function() written like this
final VoidCallback addTd.
.
or can use ValueChanged<T> which is basically void Function(T value)
More info at:
https://dart.dev/guides/language/language-tour#functions
I have a basic stateful app, with a GoogleMap base, which opens a BottomSheet on FloatingActionButton press. This is used as a settings tab to change/filter some lists which are then used by the map. My Provider is setup and values can be read/saved from both the GoogleMap dart file, and the BottomSheet dart file.
My main problem is how to provide context to the Provider.of() when not in the Widget Tree. For example, the GoogleMap runs onMapCreated: _onMapCreated, when finished loading. From within that function I want to pull a value from the Provider.of() a String, which tells me which list to use (which then populates the markers).
Things I'm trying to do:
onMapCreated() pull value from the Provider (which is later used in a DB sqflite query)
The bottomsheet needs to callback and updateMarkers() somehow, providing correct context
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
GoogleMapController mapController;
void _onMapCreated(GoogleMapController controller) {
mapController = controller;
print(Provider.of<MyAppStateContainer>(context, listen:false).getSomeValue); <---- Doesn't work
...
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MyAppStateContainer>(
create: (contextooo) => MyAppStateContainer(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('My Map'),
backgroundColor: Colors.green[700],
),
//Put in a stack widget so can layer other widgets on top of map widget
body: Stack(
children: <Widget>[
//Builder so we can get a CTX made, and then Provider.of() finds the Provider
Builder(
builder: (context) => GoogleMap(
mapType: Provider.of<MyAppStateContainer>(context).getMapType, <--- Works fine
markers: _markers,
onMapCreated: _onMapCreated,
...
}
Provider:
class MyAppStateContainer extends ChangeNotifier {
MyAppStateContainer();
MapType _mapType = MapType.terrain;
String _someValue;
MapType get getMapType => _mapType;
String get getSomeValue => _someValue;
}
I've tried all sorts of combinations of passing back BuildContext context but sadly I'm forever getting this error about the Widget Tree:
Unhandled Exception: Error: Could not find the correct Provider<MyAppStateContainer> above this MyApp Widget
The problem is that within _onMapCreated, you are trying to access the Provider using the context of MyApp, which is higher up the hierarchy of widgets than the Provider itself.
Convert your home widget (the Scaffold and everything below it) into a separate StatefulWidget and everything should start working, as you'll be using the context of the new widget, which is lower down the hierarchy of widgets than the Provider.
I'm new in Flutter and I wonder if you can help me to find a solution to my problem.
I have a scaffold with a tabview and I want to make a filter (actions button) in the main appbar to manage tabs.
Both main and child are StatefulWidgets
here's my code of the main page :
Widget body = TabBarView(
controller: tabController,
children: <Widget>[
new First(filter: filter),
new Second(filter: filter);
new Third(filter: filter);
],
);
return Scaffold(
appBar: AppBar(
actions:[IconButton(icon: Icon(MdiIcons.filter),onPressed:
() {
// Send info to child
})],
bottom: new TabBar(
controller: tabController,
tabs: <Tab>[
new Tab(text:"Tab1"),
new Tab(text:"Tab2"),
new Tab(text:"Tab3")
],
body: new Builder(builder: (BuildContext context) {
_scaffoldContext = context;
return body;
}));
));
Children page :
Widget body = Text("Filter name here");
return Scaffold(
body:
new Builder(
builder: (context) {
snack = new DisplaySnackBar(context: context);
return body;
}),
);
}
I don't know where to put the actions of appbar to make Text on child's body changes from the main page.
I found the solution by making my parent widget as an InheritedWidget so I can access to it's data from child.
Here's an example :
class MyStatefulWidget extends StatefulWidget{
#override
MyStatefulWidgetState createState() => MyStatefulWidgetState();
static MyStatefulWidgetState of(BuildContext context){
return (context.inheritFromWidgetOfExactType(_MyInheritedStateContainer) as _MyInheritedStateContainer).data;
}
}
class MyStatefulWidgetState extends State<MyStatefulWidget>{
String variableCalledHello = "Hello World";
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return new _MyInheritedStateContainer(data:this,child:/* Body here */);
}
}
class _MyInheritedStateContainer extends InheritedWidget{
_MyInheritedStateContainer({
Key key,
#required Widget child,
#required this.data,
}) : super(key: key, child: child);
final MyStatefulWidgetState data;
#override
bool updateShouldNotify(_MyInheritedStateContainer oldWidget) {
return true;
}
}
and for every child :
you just have to call MyStatefulWidgetState state = MyStatefulWidgetState.of(context);
example :
#override
Widget build(BuildContext context) {
MyStatefulWidgetState state = MyStatefulWidgetState.of(context);
print(state.variableCalledHello);
return /* Container ... */;
}
Hope that will help someone to solve his problem.
Thanks.
Please, consider the below code.
A textController is created during initState. If a button is pressed, another textController is created, inside of setState:
import 'package:flutter/material.dart';
void main() { runApp(Test()); }
class Test extends StatefulWidget {
TestState createState() => TestState();
}
class TestState extends State<Test> {
TextEditingController textController;
void initState() {
print("initState");
super.initState();
textController = TextEditingController(text: "1st textController");
}
void dispose() {
print("dispose");
textController.dispose();
super.dispose();
}
void onPressed() {
print("onPressed");
setState(() {
print("setState");
// It breaks if this line is uncommented.
if (textController != null) textController.dispose();
textController = TextEditingController(text: "2nd textController");
});
}
Widget build(BuildContext context) {
print("build");
var button = MaterialButton(onPressed: onPressed, child: const Text("Click Me"));
var textField = TextField(keyboardType: TextInputType.number, controller: textController);
return MaterialApp(
home: Material(
child: Padding(
padding: const EdgeInsets.all(30.0),
child: Column(children: [button, textField]),
),
),
);
}
}
It works. However, I've never disposed of the old textController. I can do that inside of setState, before creating the new textController:
setState(() {
print("setState");
if (textController != null) textController.dispose();
textController = TextEditingController(text: "2nd textController");
});
However, then, I get an error:
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞══
I/flutter ( 4645): The following assertion was thrown building
InputDecorator(decoration: InputDecoration(), isFocused:
I/flutter ( 4645): false, isEmpty: false, state:
_InputDecoratorState#8195a(tickers: tracking 2 tickers)):
I/flutter ( 4645): A TextEditingController was used after being disposed.
I/flutter ( 4645): Once you have called dispose() on a TextEditingController, it can no longer be used.
My questions:
1) Why am I getting this error? Is the textControlled still being used? Where?
2) How to fix this?
I've got a couple of observations for you that might help, plus, I've prepared a code sample.
First, there is no need to recreate the TextEditingController, as usually there's going to be one for each TextField, or TextFormField (depending on the implementation). You could also declare it as final without the need for the use of initState().
Second, remember to dispose of the TextEditingController inside dispose() when it is no longer needed. This will ensure we discard any resources used by the object. There is no need to dispose of it while in use. Remember: the controller is not there to notify listeners of the changes inside the text input field.
import 'package:flutter/material.dart';
// Main method
void main() {
runApp(App());
}
// I've made a separate App class that returns MaterialApp
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomeScreen(),
);
}
}
// HomeScreen replaces your Test
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
// TextEditingController could have been declared final here
// as well, for example:
//
// final _controller = TextEditingController(text: 'default');
//
// but take that as a suggestion for future ;)
TextEditingController _controller;
// String we'll be changing
String _mutableTextString = '';
#override
void initState() {
super.initState();
// Simple declarations
_controller = TextEditingController(text: 'default');
_mutableTextString = _controller.text;
}
#override
void dispose() {
// Call the dispose() method of the TextEditingController
// here, and remember to do it before the super call, as
// per official documentation:
// https://api.flutter.dev/flutter/widgets/TextEditingController-class.html
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
minimum: const EdgeInsets.all(30.0),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _controller,
keyboardType: TextInputType.text,
),
MaterialButton(
child: Text('CLICK ME'),
onPressed: _handleOnPressed,
),
Text(_mutableTextString),
],
),
),
),
);
}
// This method handles actions when the button is pressed
// and updates the UI
void _handleOnPressed() {
setState(() {
_mutableTextString = _controller.text;
});
}
}
I've got a short demo for you as well, hopefully, it helps!
Why am I getting this error? Is the textControlled still being
used? Where?
From what I've seen in your implementation, you are creating the TextController in a setState call. It should be created once in initState and reusing it during the lifetime of your stateful widget. Here is the part where the error occurs:
void onPressed() {
print("onPressed");
setState(() {
print("setState");
// It breaks if this line is uncommented.
if (textController != null) textController.dispose();
textController = TextEditingController(text: "2nd textController");
});
}
Since the textController has a String value of "1st textController" that was set here:
void initState() {
print("initState");
super.initState();
textController = TextEditingController(text: "1st textController");
}
Every time you call the onPressed(), this line:
if (textController != null) textController.dispose();
checks the value of textController. Since it is not null, the textController was disposed by the disposed() method. There is no way that this next line will run successfully:
textController = TextEditingController(text: "2nd textController");
How to fix this?
Currently, you have implemented the dispose() inside onPressed() method. If you'll check closely the documentation for dispose() method, it states that:
Called when this object is removed from the tree permanently.
The framework calls this method when this
State
object will never build again. After the framework calls
dispose,
the State
object is considered unmounted and the
mounted
property is false. It is an error to call
setState
at this point. This stage of the lifecycle is terminal: there is no
way to remount a State object that has been disposed.
Re using your code, what I did is implement the dispose() in the dispose() method as described in the documentation:
void dispose() {
print("dispose");
// This is the line that breaks when called inside setState() of onPressed() method
if (textController != null) textController.dispose();
super.dispose();
}
So your code will look like this way:
import 'package:flutter/material.dart';
void main() {
runApp(Test());
}
class Test extends StatefulWidget {
TestState createState() => TestState();
}
class TestState extends State<Test> {
TextEditingController textController;
void initState() {
print("initState");
super.initState();
textController = TextEditingController(text: "1st textController");
}
void onPressed() {
print("onPressed");
setState(() {
print("setState");
print(textController);
// It breaks if this line is uncommented.
// if (textController != null) textController.dispose();
textController = TextEditingController(text: "2nd textController");
});
}
void dispose() {
print("dispose");
// This is the line that breaks when called inside setState() of onPressed() method
if (textController != null) textController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
print("build");
var button =
MaterialButton(onPressed: onPressed, child: const Text("Click Me"));
var textField = TextField(
keyboardType: TextInputType.number, controller: textController);
return MaterialApp(
home: Material(
child: Padding(
padding: const EdgeInsets.all(30.0),
child: Column(children: [button, textField]),
),
),
);
}
}
And it's working like this:
To answer why
Here is what's happening:
You dispose the TextEditingController. It's OK by itself.
The app is rebuilt.
The TextField sees its controller changed.
It stops listening to the old controller by calling removeListener on it.
The controller checks if it is disposed and breaks:
So the rule is to never dispose controllers until they stop being listened to. And this can only happen after everything is rebuilt with new controllers.
To answer how to fix
You say that
In the real code I need to substitute a subclass of TextEditingController with another different subclass.
I see the following options:
Ideally, TextEditingController (as any ValueNotifier) should go hand-in-hand with its widget. So you may have your own stateful widget instead of that TextField, create its controller there and dispose it there. When you need to replace the controller, replace the whole widget. This way, Flutter framework takes care of disposal.
Make your own super-ValueNotifier that aggregates your different subclasses, exposes different TextEditingController instances in different states of your screen, and disposes them all in its dispose(). Create it once in your initState(), dispose it once in dispose().
Keep a list of old controllers and dispose them all in your state's dispose() method as the last resort. But it makes it all too messy.
TextEditingController is something that maintains the value, allows to change it and to know that it is changed. That's it. If you want to replace it while the screen is still shown, it means you are treating it as something else which can bring problems.
I'm trying to do what I think is a very simple Flutter app, but I can't figure out what's going wrong with my code.
I have a Flutter app with a Drawer widget. I'm using this widget to make a Navigation drawer, the Drawer has a ListView as a child and the ListView contains the View options (A, B and C) which the user can select.
Since the main page of the app (MyHomePage) extends from StatefulWidget the only thing that I do to load a new view is to call the setState method to assign the new value of my "control variable" (viewName), then I expect that Flutter executes the Widget build(BuildContext context) method of MyHomePage but with the new value of viewName this time.
All of the above works as expected, the problem with this is that in the body field of the Scaffold I have a TabBarView widget, since I want to show to the user a view with two tabs (Tab 1 and Tab 2) per each view (A, B and C).
The TabBarView children are:
-A StatefulTab object for Tab 1
-A simple Center widget for Tab 2
What I want to demonstrate here is that when you tap an option of the Drawer (Load the B view for example) the Tab 2 changes as a expected it's value, but the Tab 1 (that contains a stateful widget) not changes it's value when you tap any other option of the Drawer
Note: I must use the same StatefulTab widget for the 3 views in order to reuse the code, since the only value which changes for the 3 views is the viewName variable.
Here is the code:
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/StatefulTab.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(viewName: 'A'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.viewName}) : super(key: key);
final String viewName;
#override
_MyHomePageState createState() => new _MyHomePageState(viewName: viewName);
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
String viewName;
_MyHomePageState({Key key, this.viewName});
TabController controller;
#override
void initState() {
super.initState();
controller = new TabController(
length: 2,
vsync: this,
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
var tabs = <Tab>[
new Tab(icon: new Icon(Icons.home), text: 'Tab 1'),
new Tab(icon: new Icon(Icons.account_box), text: 'Tab 2')
];
return new Scaffold(
appBar: new AppBar(
title: new Text(viewName),
),
body: new TabBarView(controller: controller, children: <Widget>[
new StatefulTab(viewName: viewName),
new Center(child: new Text('This is the Tab 2 for view $viewName'))
]),
bottomNavigationBar: new Material(
color: Colors.blue,
child: new TabBar(controller: controller, tabs: tabs),
),
drawer: new Drawer(
child: new ListView(
children: <Widget>[
new Padding(
padding: new EdgeInsets.only(top: 50.0),
),
new ClipRect(
child: new Column(
children: <Widget>[
new ListTile(
title: new Text('Tap to load view: A'),
onTap: () => _loadView('A', context),
),
new ListTile(
title: new Text('Tap to load view: B'),
onTap: () => _loadView('B', context),
),
new ListTile(
title: new Text('Tap to load view: C'),
onTap: () => _loadView('C', context),
),
],
),
),
],
),
),
);
}
_loadView(String view, BuildContext context) {
Navigator.of(context).pop();
setState(() {
if (view == 'A') {
viewName = 'A';
} else if (view == 'B') {
viewName = 'B';
} else if (view == 'C') {
viewName = 'C';
}
});
}
}
StatefulTab.dart
import 'package:flutter/material.dart';
class StatefulTab extends StatefulWidget {
String viewName;
StatefulTab({Key key, this.viewName}) : super(key: key);
#override
StatefulTabState createState() => new StatefulTabState(viewName: viewName);
}
class StatefulTabState extends State<StatefulTab> {
String viewName;
StatefulTabState({Key key, this.viewName});
#override
Widget build(BuildContext context) {
return new Center(
child: new Text('This is the Tab 1 for View $viewName'),
);
}
}
How can I tell Flutter that takes the new value for the stateful wdiget of the Tab 1?
Is there a better way to implement a Navigation drawer with dynamic views?
Thanks in advance!
I think I found your problem. You keep the viewName as State in your Homepage and additionally in the StatefulTab. This can't really work, because for the StatefulTab the state doesn't change only because the state of the HomePage changes. I came to that conclusion by inserting print statements in the two build methods. The build method of the HomePage acts according to your desired behavior (as you already saw in the header of the scaffold), but the build method of the StatefulTab kept its state.
Further investigating and various print statements in various places led me to the conclusion, that the constructor of the StatefulTabState is not called after one of the drawer buttons is clicked. Here is a working example of your StatefulTab:
class StatefulTab extends StatefulWidget {
String viewName;
StatefulTab({Key key, this.viewName}) : super(key: key);
#override
StatefulTabState createState() => new StatefulTabState();
}
class StatefulTabState extends State<StatefulTab> {
#override
Widget build(BuildContext context) {
return new Center(
child: new Text('This is the Tab 1 for View ${widget.viewName}'),
);
}
}
Don't hesitate to comment, if you have any questions. It may be beneficial for you to have a look at this tutorial/documentation.
For implement TabBar and drawer flutter provide us following widget.
1. TabController
2. TabBar
3. Drawer
I found a simple demo of TabBar and drawer.