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
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;
}
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;
});
});
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
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
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 */
),
);
}
}