Behaivor of instances new-ed from the same StatefulWidget - flutter

I have troubled with the behaivor of instances from the same class.
I made two instances of StatefulWidget(BodyLayout) class.
and switch them by BottomNavigationBar
But only one of initState() of BodyLayout is called.
I am confused by this behavior , State is shared each instances???
I want to each initState() is called separately.
Please help some hint.
These are full source code below.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
"/": (_) => new MyHomePage(),
"/browser": (_) => new Text("not use"),
}
);
}
}
class Article{
String title;
String url;
Article();
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> _myLayouts = [];
int _currentIndex = 0;
#override
void initState() {
super.initState();
_myLayouts = [
new BodyLayout("latest"),
new BodyLayout("pop"),
];
}
void _onItemTapped(int index) {
print("itemTapped :" +index.toString());
setState(() {
_currentIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// title: Text(widget.title),
),
body: _myLayouts[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('latest'),
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
title: Text('pop'),
),
],
currentIndex: _currentIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
);
}
}
class BodyLayout extends StatefulWidget {
final String mode;
BodyLayout(this.mode);
#override
_BodyLayoutState createState() => _BodyLayoutState();
}
class _BodyLayoutState extends State<BodyLayout>{
List<Article> articles = [];
bool loading = true;
int page = 1;
#override
void initState(){
super.initState();
print ("init:" + widget.mode);// this called only one time.....
_callApi(); // this called only one time.....
}
void _callApi() {
var a = Article();
a.title = widget.mode;
a.url = widget.mode;
articles.add(a);
setState((){
loading = false;
});
}
#override
Widget build(BuildContext context) {
if(loading) {
return CircularProgressIndicator();
}
return ListView.builder(
itemCount: articles.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(articles[index].title),
);
},
);
}
}

I am confused by this behavior , State is shared each instances???
Yes, it is. You need to provide unique keys to your widgets:
class BodyLayout extends StatefulWidget {
BodyLayout(this.mode, {Key key}) : super(key: key);
...
_myLayouts = [
new BodyLayout("latest", key: Key('1')),
new BodyLayout("pop", key: Key('2')),
];
and bam - it works:
I/flutter (12871): init:latest
I/flutter (12871): itemTapped :1
I/flutter (12871): init:pop
From the docs:
A StatefulWidget keeps the same State object when moving from one location in the tree to another if its creator used a GlobalKey for its key.
So basically, if your widgets have the same key (or don't have one), they're interpreted as the same widget.

Related

How to Make flutter webview load only once?

when I switch from one page to another the state of my WebView isn't remembered and it reloads every time. how can prevent that?
I was expecting the web view to load only for the first time and remember it's state but it gets reloaded every time i navigate to that screen. how to make it remember the state?
You can achieve this with PageView with AutomaticKeepAliveClientMixin.
See this example app:
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final bucket = PageStorageBucket();
MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
final PageController _controller = PageController();
int _selectedIndex = 0;
void _onItemTapped(int index) {
setState(() {
_controller.jumpToPage(index);
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
// selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'home'),
BottomNavigationBarItem(icon: Icon(Icons.web), label: 'web'),
]),
body: PageView(
controller: _controller,
children: const [MyDummyPage(), MyWebView()],
),
);
}
}
class MyDummyPage extends StatefulWidget {
const MyDummyPage({super.key});
#override
State<MyDummyPage> createState() => _MyDummyPageState();
}
class _MyDummyPageState extends State<MyDummyPage>
with AutomaticKeepAliveClientMixin {
late int count;
#override
void initState() {
super.initState();
count = 0;
}
#override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('$count'),
],
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () => setState(() {
count++;
}),
),
);
}
#override
bool get wantKeepAlive => true;
}
class MyWebView extends StatefulWidget {
const MyWebView({super.key});
#override
State<MyWebView> createState() => _MyWebViewState();
}
class _MyWebViewState extends State<MyWebView>
with AutomaticKeepAliveClientMixin {
#override
Widget build(BuildContext context) {
super.build(context);
return const WebView(initialUrl: 'https://flutter.dev');
}
#override
bool get wantKeepAlive => true;
}

Flutter: setState() called in constructor

I am novice in Flutter, and ran upon a problem trying to show a map and implement some tilelayers.
The app has a Drawer implemtation where I want to enable/disable and clear the tile cache.
I have fetched some examples where this was working well, so I know that the tiling works great, but here i ran upon a problem where I want to call member functions of the MyWorldMap stateful widget from the drawer widget, and to my spare knowledge I now are plagued by the setState() called in constructor error message.
Do you have any suggestions to help, or guide me on the correct path ?
Note !! Remember to add your own MAP API KEY according to: https://codelabs.developers.google.com/codelabs/google-maps-in-flutter?hl=en&continue=https%3A%2F%2Fcodelabs.developers.google.com%2F#3
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget{
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title:"My App test",
theme: ThemeData(primarySwatch: Colors.blue),
home: HomePage(title: "My World Map")
);
}
}
class HomePage extends StatefulWidget{
final String title;
HomePage({Key? key, required this.title}):super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>{
#override
void initState(){
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: MainDrawer(),
body: MyWorldMap(),
);
}
}
class MainDrawer extends StatefulWidget{
#override
State<StatefulWidget> createState() => MainDrawerState();
}
class MainDrawerState extends State<MainDrawer>{
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
const DrawerHeader(
decoration: BoxDecoration(color: Colors.blue),
child: Text("My World Map"),
),
ListTile(
title: const Text ("Add tile overlay"),
onTap: () => addTileOverlay(),
),
ListTile(
title: const Text ("Clear tile overlay cache"),
onTap: () => clearTileCache(),
),
ListTile(
title: const Text ("Remove tile overlay"),
onTap: () => removeTileOverlay(),
),
],
),
);
}
void addTileOverlay(){
print("Attempting to add tile overlay");
MyWorldMap().addTileOverlay();
}
void clearTileCache(){
print("Attempting clear tile cache");
MyWorldMap().clearTileCache();
}
void removeTileOverlay(){
print("Attempting removing tile overlay");
MyWorldMap().removeTileOverlay();
}
}
class MyWorldMap extends StatefulWidget{
const MyWorldMap({Key? key}) : super(key: key);
addTileOverlay() => createState()._addTileOverlay();
removeTileOverlay() => createState()._removeTileOverlay();
clearTileCache() => createState()._clearTileCache();
#override
_MyWorldMapState createState() => _MyWorldMapState();
}
class _MyWorldMapState extends State<MyWorldMap>
{
TileOverlay? _tileOverlay;
late GoogleMapController _mapController;
final LatLng _initialCameraPosition = const LatLng(61.9026,6.7003); //Change with your location
//You need to change maps API key in AndroidManifest.xml
#override
void initState(){
super.initState();
}
Future<void> _onMapCreated(GoogleMapController controller) async {
_mapController = controller;
setState(() {
//Do stuff ?
});
}
#override
Widget build(BuildContext context) {
Set<TileOverlay> overlays = <TileOverlay>{
if(_tileOverlay != null) _tileOverlay!,
};
return GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: CameraPosition(
target: _initialCameraPosition,
zoom:15,
),
myLocationEnabled: false,
tileOverlays: overlays,
);
}
void _addTileOverlay()
{
final TileOverlay tileOverlay = TileOverlay(
tileOverlayId: TileOverlayId("My World Map Overlay"),
tileProvider: MyWorldMapTileProvider(),
);
setState((){ //The code fails here when pushing the 'Add tile overlay button' !!
_tileOverlay = tileOverlay;
});
}
void _clearTileCache()
{
if(_tileOverlay != null){
_mapController.clearTileCache(_tileOverlay!.tileOverlayId);
}
}
void _removeTileOverlay()
{
setState(() {
_tileOverlay = null;
});
}
}
class MyWorldMapTileProvider implements TileProvider {
#override
Future<Tile> getTile(int x, int y, int? zoom) async {
String path = 'https://maptiles1.finncdn.no/tileService/1.0.1/norortho/$zoom/$x/$y.png';
http.Response response = await http.get(
Uri.parse(path)
);
return Tile(x,y,response.bodyBytes);
}
}
Not that I am a real professional with flutter, but I think the problem might reside in here:
addTileOverlay() => createState()._addTileOverlay();
removeTileOverlay() => createState()._removeTileOverlay();
clearTileCache() => createState()._clearTileCache();
You're creating a new state each time you invoke one of those methods in MyWorldMap widget, and I don't think that's the correct behaviour.
If you want to edit a Widget state from another Widget, you should try using keys: I think any stateful Widget can take a key argument in the constructor, that can be used in turn to change its state from other widgets. I'll try writing a simple example.
class Parent extends StatelessWidget {
final keyA = GlobalKey();
final keyB = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: [
ChildA(keyA),
ChildB(keyB, keyA),
]),
);
}
}
class ChildA extends StatefulWidget {
const ChildA(GlobalKey key) : super(key: key);
#override
State<StatefulWidget> createState() => ChildAState();
}
class ChildAState extends State<ChildA> {
int counter = 0;
#override
Widget build(BuildContext context) {
return Text("Child A count: $counter");
}
void increaseCounter(){
setState(() {
counter++;
});
}
}
class ChildB extends StatefulWidget {
final GlobalKey childAKey;
const ChildB(GlobalKey key, this.childAKey) : super(key: key);
#override
State<StatefulWidget> createState() => ChildBState();
}
class ChildBState extends State<ChildB> {
#override
Widget build(BuildContext context) {
return TextButton(
child: const Text("Press here"),
onPressed: () {
(widget.childAKey.currentState as ChildAState).increaseCounter();
},
);
}
}
After #il_boga lead me to the answer (all credits to him), I'll post the working code here:
I moved the TileOverlay creation to initState of _MyWorldMapState class, and added a buffered 'layer' too so I could switch on/off the layer by setting _mapTileOverlay to null when removing and back to _bufferedMapTileOverlay when adding the overlay.
Further I have created two GlobalKeys (actually not knowing why i need drawerKey actually, since I never activily reference it anywhere..., mapKey is obvious)
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget{
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title:"My App test",
theme: ThemeData(primarySwatch: Colors.blue),
home: HomePage(title: "My World Map")
);
}
}
class HomePage extends StatefulWidget{
final String title;
const HomePage({Key? key, required this.title}):super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>{
final drawerKey = GlobalKey();
final mapKey = GlobalKey();
#override
void initState(){
print("_HomePageState(): initState");
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: MainDrawer(drawerKey: drawerKey, mapKey: mapKey,),
body: MyWorldMap(mapKey: mapKey,),
);
}
}
class MainDrawer extends StatefulWidget{
final GlobalKey mapKey;
const MainDrawer({required GlobalKey drawerKey, required this.mapKey}) : super(key: drawerKey);
#override
State<StatefulWidget> createState() => MainDrawerState();
}
class MainDrawerState extends State<MainDrawer>{
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
const DrawerHeader(
decoration: BoxDecoration(color: Colors.blue),
child: Text("My World Map"),
),
ListTile(
title: const Text ("Add tile overlay"),
onTap: () => addTileOverlay(),
),
ListTile(
title: const Text ("Clear tile overlay cache"),
onTap: () => clearTileCache(),
),
ListTile(
title: const Text ("Remove tile overlay"),
onTap: () => removeTileOverlay(),
),
],
),
);
}
void addTileOverlay(){
print("Attempting to add tile overlay");
//MyWorldMap().addTileOverlay();
(widget.mapKey.currentState as _MyWorldMapState)._addTileOverlay();
}
void clearTileCache(){
print("Attempting clear tile cache");
//MyWorldMap().clearTileCache();
(widget.mapKey.currentState as _MyWorldMapState)._clearTileCache();
}
void removeTileOverlay(){
print("Attempting removing tile overlay");
//MyWorldMap().removeTileOverlay();
(widget.mapKey.currentState as _MyWorldMapState)._removeTileOverlay();
}
}
class MyWorldMap extends StatefulWidget{
const MyWorldMap({required GlobalKey mapKey}) : super(key: mapKey);
//addTileOverlay() => createState()._addTileOverlay();
//removeTileOverlay() => createState()._removeTileOverlay();
//clearTileCache() => createState()._clearTileCache();
#override
_MyWorldMapState createState() => _MyWorldMapState();
}
class _MyWorldMapState extends State<MyWorldMap>
{
TileOverlay? _bufferedMapTileOverlay; //intermediate, which actually holds the overlay
TileOverlay? _mapTileOverlay; //value which connects to the map
late GoogleMapController _mapController;
final LatLng _initialCameraPosition = const LatLng(61.9026,6.7003); //Change with your location
//You need to change maps API key in AndroidManifest.xml
#override
void initState(){
print("_MyWordMapState(): initState");
super.initState();
final TileOverlay newMapTileOverlay = TileOverlay( //Inits the tileOverlay
tileOverlayId: const TileOverlayId("My World Map Overlay"),
tileProvider: MyWorldMapTileProvider(),
);
_bufferedMapTileOverlay = newMapTileOverlay;
}
Future<void> _onMapCreated(GoogleMapController controller) async {
_mapController = controller;
setState(() {
//Do stuff ?
});
}
#override
Widget build(BuildContext context) {
Set<TileOverlay> overlays = <TileOverlay>{ //connect a set of overlays (here just one)
if(_mapTileOverlay != null) _mapTileOverlay!,
};
return GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: CameraPosition(
target: _initialCameraPosition,
zoom:15,
),
myLocationEnabled: false,
tileOverlays: overlays, //connect to the set of overlays (I have only one (see above))
);
}
void _addTileOverlay()
{
setState((){
_mapTileOverlay = _bufferedMapTileOverlay;
});
}
void _clearTileCache()
{
if(_mapTileOverlay != null){
print("Clearing tile cache");
_mapController.clearTileCache(_mapTileOverlay!.tileOverlayId);
}
}
void _removeTileOverlay()
{
setState(() {
_mapTileOverlay = null;
});
}
}
class MyWorldMapTileProvider implements TileProvider {
#override
Future<Tile> getTile(int x, int y, int? zoom) async {
String path = 'https://maptiles1.finncdn.no/tileService/1.0.1/norortho/$zoom/$x/$y.png';
http.Response response = await http.get(
Uri.parse(path)
);
return Tile(x,y,response.bodyBytes);
}
}
Seems like you are using setState before build method has finished building the widgets. I would suggest using setState after build has finished, this way :
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
setState(() {
// do stuff;
});
});

Where content of StatefulWidget should be stored?

I switch two layout by BottomNavigationBar
Every time switching the bar, it loads initState() and lost all variables in _BodyLayoutState()
So, I wonder,
1.Keeping the contents(List<Article> articles = [];) in State is not good?? I should keep contents in upper class like _MyHomePageState?
2.Is there any way to keep contents in State and not dispose when switching ???
These are source code.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
"/": (_) => new MyHomePage(),
"/browser": (_) => new Text("not use"),
}
);
}
}
class Article{
String title;
String url;
Article();
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> _myLayouts = [];
int _currentIndex = 0;
#override
void initState() {
super.initState();
_myLayouts = [
new BodyLayout("latest", key: Key('1')),
new BodyLayout("pop",key: Key('2')),
];
}
void _onItemTapped(int index) {
print("itemTapped :" +index.toString());
setState(() {
_currentIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// title: Text(widget.title),
),
body: _myLayouts[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('latest'),
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
title: Text('pop'),
),
],
currentIndex: _currentIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
);
}
}
class BodyLayout extends StatefulWidget {
final String mode;
BodyLayout(this.mode, {Key key}) : super(key: key);
#override
_BodyLayoutState createState() => _BodyLayoutState();
}
class _BodyLayoutState extends State<BodyLayout>{
List<Article> articles = [];
bool loading = true;
bool firstLoaded = false;
int page = 1;
#override
void initState(){
super.initState();
print ("init:" + widget.mode);
if (firstLoaded == false){
print("I don't want to load twice");
_callApi();
}
}
void _callApi() {
var a = Article();
a.title = widget.mode;
a.url = widget.mode;
articles.add(a);
firstLoaded == true;
setState((){
loading = false;
});
}
#override
Widget build(BuildContext context) {
if(loading) {
return CircularProgressIndicator();
}
return ListView.builder(
itemCount: articles.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(articles[index].title),
);
},
);
}
}
You can try IndexedStack to display your BodyLayout's.
Just change
body: _myLayouts[_currentIndex],
to
body: IndexedStack(children: _myLayouts, index: _currentIndex,),
You can use the Provider package to store that information https://pub.dev/packages/provider

Flutter Quick Actions change selected Bottom Navigation Bar item

I'm trying to implement home screen quick actions / app shortcuts in my Flutter app. What I'm trying to achieve is when the user launches my app via a quick action, the app changes the selected tab inside the bottom navigation bar. Any help is appreciated.
main.dart:
runApp(
MaterialApp(
theme: Themes.appLightTheme,
darkTheme: Themes.appDarkTheme,
home: QuickActionsController(
child: HomeFrame(currentIndex: 0),
),
My QuickActionsController class:
import 'package:binfinder/screens/HomeFrame.dart';
import 'package:flutter/material.dart';
import 'package:quick_actions/quick_actions.dart';
class QuickActionsController extends StatefulWidget {
final HomeFrame child;
QuickActionsController({Key key, this.child}) : super(key: key);
#override
_QuickActionsControllerState createState() => _QuickActionsControllerState();
}
class _QuickActionsControllerState extends State<QuickActionsController> {
final QuickActions quickActions = QuickActions();
int _currentIndex = 0;
#override
void initState() {
super.initState();
_handleQuickActions();
_setupQuickActions();
}
void _setupQuickActions() {
quickActions.setShortcutItems(<ShortcutItem>[
ShortcutItem(
type: 'action_map',
localizedTitle: 'Map',
),
]);
}
void _handleQuickActions() {
quickActions.initialize((shortcutType) {
if (shortcutType == 'action_map') {
setState(() {
_currentIndex = 1;
});
} else {
setState(() {
_currentIndex = 0;
});
}
});
}
#override
Widget build(BuildContext context) {
widget.child.currentIndex = _currentIndex;
return widget.child;
}
}
In the demo below, direct click app will enter First Page and In Quick Action choose Main view will enter Second Page
_handleQuickActions need to use
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => BottomNavigationBarController(
initialIndex: 1,
)));
and use initial index to control page index
class BottomNavigationBarController extends StatefulWidget {
final int initialIndex;
BottomNavigationBarController({
this.initialIndex,
Key key,
}) : super(key: key);
#override
_BottomNavigationBarControllerState createState() =>
_BottomNavigationBarControllerState();
}
full code
import 'package:flutter/material.dart';
import 'package:quick_actions/quick_actions.dart';
import 'dart:io';
class QuickActionsManager extends StatefulWidget {
final Widget child;
QuickActionsManager({Key key, this.child}) : super(key: key);
_QuickActionsManagerState createState() => _QuickActionsManagerState();
}
class _QuickActionsManagerState extends State<QuickActionsManager> {
final QuickActions quickActions = QuickActions();
#override
void initState() {
super.initState();
_setupQuickActions();
_handleQuickActions();
}
#override
Widget build(BuildContext context) {
return widget.child;
}
void _setupQuickActions() {
quickActions.setShortcutItems(<ShortcutItem>[
ShortcutItem(
type: 'action_main',
localizedTitle: 'Main view',
icon: Platform.isAndroid ? 'quick_box' : 'QuickBox'),
ShortcutItem(
type: 'action_help',
localizedTitle: 'Help',
icon: Platform.isAndroid ? 'quick_heart' : 'QuickHeart')
]);
}
void _handleQuickActions() {
quickActions.initialize((shortcutType) {
if (shortcutType == 'action_main') {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => BottomNavigationBarController(
initialIndex: 1,
)));
} else if (shortcutType == 'action_help') {
print('Show the help dialog!');
}
});
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'QuickActions Demo',
home: QuickActionsManager(child: BottomNavigationBarController(initialIndex: 0,)));
}
}
class Home extends StatelessWidget {
const Home({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: Text('Home')));
}
}
class Login extends StatelessWidget {
const Login({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: Text('Login')));
}
}
class BottomNavigationBarController extends StatefulWidget {
final int initialIndex;
BottomNavigationBarController({
this.initialIndex,
Key key,
}) : super(key: key);
#override
_BottomNavigationBarControllerState createState() =>
_BottomNavigationBarControllerState();
}
class _BottomNavigationBarControllerState
extends State<BottomNavigationBarController> {
final List<Widget> pages = [
FirstPage(
key: PageStorageKey('Page1'),
),
SecondPage(
key: PageStorageKey('Page2'),
),
];
final PageStorageBucket bucket = PageStorageBucket();
int _selectedIndex = 0;
Widget _bottomNavigationBar(int selectedIndex) => BottomNavigationBar(
onTap: (int index) => setState(() => _selectedIndex = index),
currentIndex: selectedIndex,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.add), title: Text('First Page')),
BottomNavigationBarItem(
icon: Icon(Icons.list), title: Text('Second Page')),
],
);
#override
void initState() {
_selectedIndex = widget.initialIndex;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: _bottomNavigationBar(_selectedIndex),
body: PageStorage(
child: pages[_selectedIndex],
bucket: bucket,
),
);
}
}
class FirstPage extends StatelessWidget {
const FirstPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("First Screen"),
),
body: ListView.builder(itemBuilder: (context, index) {
return ListTile(
title: Text('Lorem Ipsum'),
subtitle: Text('$index'),
);
}),
);
}
}
class SecondPage extends StatelessWidget {
const SecondPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: ListView.builder(itemBuilder: (context, index) {
return ListTile(
title: Text('Lorem Ipsum'),
subtitle: Text('$index'),
);
}),
);
}
}
demo, emulator is a little slow when enter Second Page

Flutter: How to pass data from the parent stateful widget to one of the tabs in the BottomNavigationBar?

import 'package:flutter/material.dart';
class HomeScreen extends StatefulWidget {
HomeScreen();
#override
_HomeScreenState createState() => new _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _currentIndex = 0;
final List<Widget> _children = [
MapsScreen(),
HistoryScreen(),
];
#override
void initState() {
super.initState();
RestAPI.loadMapsFromNetwork();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home screen'),
),
body: _children[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped, // new
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.map),
title: new Text('Maps'),
),
BottomNavigationBarItem(
icon: new Icon(Icons.change_history),
title: new Text('History'),
)
],
),
);
}
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
}
This home.dart makes a network call in initState method.
How do I pass the list of maps that the client received from the network to one of the tabs like MapsScreen? Do I need to use ScopedModel or InheritedWidget or is there a better approach? All the logic to render is within the MapsScreen class.
You can pass the value from the json response like this.
class MapScreen extends StatefulWidget {
Map<List<String,dynamic>> data ;
MapScreen({this.data}) ;
_MapScreenState createState() => _MapScreenState() ;
}
class _MapScreenState extends State<MapScreen> {
#override
Widget build(BuildContext context) {
return Container(
child: ListView(
/* use the data over here */
),
);
}
}