Flutter's BottomNavigationBar not updating selected item on tap - flutter

I'm new at Flutter and currently trying very simple exercises to understand the basics. I have just created a basic Home with a BottomNavigationBar at, well, the screen bottom. But when I tap a bar icon, it doesn't switch colors to appear as if it was tapped. I need to reload it to update it. I can't update the icon state from 'MyBottmNavigationBar' class... If I simply copy/paste Flutter.dev sample code it works... What I need to fix in my code?
Feel free to point any errors or tips!
Thanks in advance!
import 'package:flutter/material.dart';
void main()=> runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Home();
}
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyScaffold(),
);
}
}
class MyScaffold extends StatefulWidget {
#override
_MyScaffoldState createState() => _MyScaffoldState();
}
class _MyScaffoldState extends State<MyScaffold> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Hey Flutter'),),
body: MyListView(),
bottomNavigationBar: MyBottomNavigationBar(),
);
}
}
class MyListView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(2),
children: <Widget>[
Container(
height: 50,
color: Colors.amber[600],
child: const Center(child: Text('Entry A')),
),
Container(
height: 50,
color: Colors.amber[500],
child: const Center(child: Text('Entry B')),
),
Container(
height: 50,
color: Colors.amber[100],
child: const Center(child: Text('Entry C')),
),
Container(
height: 50,
color: Colors.amber[600],
child: const Center(child: Text('Entry A')),
),
],
);
}
}
class MyBottomNavigationBar extends StatefulWidget {
MyBottomNavigationBar({Key key}) : super(key: key);
#override
_MyBottomNavigationBarState createState() => _MyBottomNavigationBarState();
}
class _MyBottomNavigationBarState extends State<MyBottomNavigationBar> {
int _selectedIndex = 0;
void _onItemTapped(int index){
_selectedIndex = index;
print('Selected: ${_selectedIndex} CurrentIndex: ${index}');
}
#override
Widget build(BuildContext context) {
return BottomNavigationBar(
currentIndex: _selectedIndex,
selectedItemColor: Colors.pink,
onTap: _onItemTapped,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.table_chart),
title: Text('Projects'),
),
BottomNavigationBarItem(
icon: Icon(Icons.people),
title: Text('Teams'),
),
],
);
}
}
```

Also in addition don't write the ontapped() in the build method as it was creates the same instance and never updates the view.

You forgot to call setState in your _onItemTapped function.
To fix this problem, replace the code below with your _onItemtapped function:
void _onItemTapped(int index){
// add the setstate callback here
setState(() {
_selectedIndex = index;
});
print('Selected: ${_selectedIndex} CurrentIndex: ${index}');
}
I hope this helps.

Related

Make bottomNavigationBar expand down to use whole screen in Flutter

I am new to Flutter and went on to do the codelabs - first flutter app
Since I'm learning Flutter to develop mobile apps, this tutorials use of NavigationRail isn't too good looking on a phone. I tried to switch it out for a BottomNavigationBar. When changing the background color of the navbar I noticed it doesnt expand to use the full screen. Is it always like this, or is there something making it display it this way in the code?Could'nt find any useful information about this case.
Is it possible to make the green background cover the, here black, area at the bottom of the screen?
Area under bar, white when debugging on real device, here it is black
The final code from the tutorial is poorly adjusted to:
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
),
home: MyHomePage(),
),
);
}
}
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
void getNext() {
current = WordPair.random();
notifyListeners();
}
var favorites = <WordPair>[];
void toggleFavorite() {
if (favorites.contains(current)) {
favorites.remove(current);
} else {
favorites.add(current);
}
notifyListeners();
}
}
class MyHomePage extends StatefulWidget {
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
#override
Widget build(BuildContext context) {
Widget page;
switch(selectedIndex){
case 0:
page = GeneratorPage();
break;
case 1:
page = FavoritesPage();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
return LayoutBuilder(
builder: (context, constraints) {
return Scaffold(
body: Center(
child: page,
),
bottomNavigationBar: BottomNavigationBar (
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.favorite),
label: 'Favorites',
),
],
currentIndex: selectedIndex,
onTap: _onItemTapped,
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
elevation: 0.0,
),
);
}
);
}
void _onItemTapped(int index){
setState(() {
selectedIndex = index;
});
}
}
class FavoritesPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
if (appState.favorites.isEmpty) {
return Center(
child: Text('No favorites yet.'),
);
}
return ListView(
children: [
Padding(
padding: const EdgeInsets.all(20),
child: Text('You have '
'${appState.favorites.length} favorites:'),
),
for (var pair in appState.favorites)
ListTile(
leading: Icon(Icons.favorite),
title: Text(pair.asLowerCase),
),
],
);
}
}
class GeneratorPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
IconData icon;
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
);
}
}
class BigCard extends StatelessWidget {
const BigCard({
Key? key,
required this.pair,
}) : super(key: key);
final WordPair pair;
#override
Widget build(BuildContext context) {
var theme = Theme.of(context);
var style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card(
color: theme.colorScheme.primary,
elevation: 10,
child: Padding(
padding: const EdgeInsets.all(20),
child: Text(pair.asLowerCase, style: style),
),
);
}
}
Tried changing elevation to 0.0, expandbody and what not. Nothing seems to be working here?
You can use SystemUiOverlayStyle class
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light
.copyWith(systemNavigationBarColor: Colors.greenAccent));
super.initState();
}

My screen doesn't reflect the changes in my app though the setState method works well

I'm trying to call a StatefulWidget(i.e FirstPage()) within a MaterialApp. I'm pretty much new to flutter and I don't know where I went wrong. According to my knownledge I've used StatefulWidget to tell flutter my screen on that page is going to encounter some changes in UI. But I got no idea to fix this error.
main.dart file:
import 'package:flutter/material.dart';
import 'package:flutter_project/main.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: FirstPage());
}
}
class FirstPage extends StatefulWidget {
const FirstPage({Key? key}) : super(key: key);
#override
State<FirstPage> createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
#override
Widget build(BuildContext context) {
String buttonName = "Click";
int currentIndex = 0;
return Scaffold(
appBar: AppBar(
title: const Text("App title "),
),
body: Center(
child: currentIndex == 0
? Container(
width: double.infinity,
height: double.infinity,
color: Colors.blueAccent,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 280,
height: 80,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
side: BorderSide.none,
borderRadius: BorderRadius.circular(18),
),
backgroundColor: const Color.fromRGBO(9, 8, 99, 90),
foregroundColor: Colors.red,
),
onPressed: () {
setState(() {
buttonName = "Clicked";
//print(buttonName0);
});
},
child: Text(buttonName),
),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
//'BuildContext' - datatype and 'context' - variable name
return const SecondPage();
},
),
);
},
child: const Text("Move to new page"),
),
],
),
)
: Image.asset("images/img.png"),
),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(label: "Home", icon: Icon(Icons.home)),
BottomNavigationBarItem(label: "Settings", icon: Icon(Icons.settings))
],
currentIndex: currentIndex,
onTap: (int index) {
//index value here is returned by flutter by the function 'onTap'
setState(() {
currentIndex = index;
//print(currentIndex);
});
},
),
); //Scaffold represents the skeleton of the app(displays white page)
}
}
class SecondPage extends StatelessWidget {
const SecondPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
);
}
}
Images:
Before pressing Click and Settings button
After pressing Click and Settings looks the same
I want the screen to change the ElevatedButton Click to Clicked when onPressed() is triggered and also, the app should be able to switch settings page when the onTap() method is triggered in the bottom navigation bar.
The code worked initially when I refrained from calling an entire page of Scaffold from Material app, but as soon as I changed the part
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: FirstPage()); //<-- this part
}
}
I'm getting this error.
Put your variables outside the build method.Else it will reset to default on every build.
It will be like
class _FirstPageState extends State<FirstPage> {
//here
String buttonName = "Click";
int currentIndex = 0;
#override
Widget build(BuildContext context) {
// Not here
return Scaffold(
appBar: AppBar(
More about StatefulWidget

Flutter: How to use a button that has the same effect as clicking on a BottomNavigationBar?

I have a Dart file named page0.dart and this only includes a BottomNavigationBar.
BottomNavigationBar has 2 items in it which redirects me to dashboard.dart and target.dart, the navigation via the BottomNavigationBar works as expected.
Now the problem: I need a button on dashboard.dart that should redirect me to target.dart, but keep the ButtomNavigationBar visible.
I am redirecting with Navigator.push, but that opens target.dart directly and skips page0.dart I think.
Screenshots are below. Please watch them for better understanding my problem.
Here are the code samples:
page0.dart:
import 'package:flutter/material.dart';
import 'package:navbartest/dashboard.dart';
import 'package:navbartest/target.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key, required String title}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return const Scaffold(
bottomNavigationBar: BottomNavBar(),
);
}
}
class BottomNavBar extends StatefulWidget {
const BottomNavBar({super.key});
#override
State<BottomNavBar> createState() => _BottomNavBarState();
}
class _BottomNavBarState extends State<BottomNavBar> {
int _pageIndex = 0;
final List<Widget> _tabList = const [
Dashboard(),
Target(),
];
Widget? onItemTap(int index) {
setState(() {
_pageIndex = index;
});
return null;
}
#override
Widget build(BuildContext context) {
return Stack(
children: [
_tabList.elementAt(_pageIndex),
Padding(
padding: EdgeInsets.only(right: 35, bottom: 25, left: 35),
child: Align(
alignment: const Alignment(0.0, 1.0),
child: ClipRRect(
borderRadius: const BorderRadius.all(
Radius.circular(20),
),
child: BottomNavigationBar(
backgroundColor: const Color(0xff565656),
type: BottomNavigationBarType.fixed,
showSelectedLabels: false,
showUnselectedLabels: false,
unselectedItemColor: Colors.white,
selectedItemColor: Colors.white,
onTap: onItemTap,
items: [
BottomNavigationBarItem(
icon: const Icon(Icons.home),
label: "Dashboard",
),
BottomNavigationBarItem(
icon: const Icon(Icons.car_repair),
label: "Target",
),
],
),
),
),
),
],
);
}
}
dashboard.dart
import 'package:navbartest/target.dart';
class Dashboard extends StatelessWidget {
const Dashboard({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: Container(
width: 120,
height: 20,
color: Colors.blue,
child: InkResponse(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const Target()),
);
},
child: Text('navigate to target'),
),
),
),
),
);
}
}
target.dart:
class Target extends StatelessWidget {
const Target({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Text('target'),
),
);
}
}
when the app is started, it looks like this
when I click the blue button to navigate, it looks like this (NavBar is gone!)
when I click the symbol in the navbar redirecting me to target.dart, it looks like this (thats how I want it with the blue button too!)
actually you need to use a state management for this type of actions , but I found a work around in your case ,
I will set the classes next Just replace them with your classes and it will work.
1 - page0.dart:
import 'target.dart';
import 'package:flutter/material.dart';
import 'dash.dart';
class BottomNavBar extends StatefulWidget {
const BottomNavBar({super.key});
#override
State<BottomNavBar> createState() => BottomNavBarState();
}
class BottomNavBarState extends State<BottomNavBar> {
late int _pageIndex;
late final List<Widget> _tabList;
Widget? onItemTap(int index) {
setState(() {
_pageIndex = index;
});
return null;
}
#override
void initState(){
super.initState();
_pageIndex = 0;
_tabList = [
Dashboard(ref:(int number){
setState(() {
_pageIndex = number;
});
}),
const Target(),
];
}
#override
Widget build(BuildContext context) {
return Stack(
children: [
_tabList.elementAt(_pageIndex),
Padding(
padding: EdgeInsets.only(right: 35, bottom: 25, left: 35),
child: Align(
alignment: const Alignment(0.0, 1.0),
child: ClipRRect(
borderRadius: const BorderRadius.all(
Radius.circular(20),
),
child: BottomNavigationBar(
backgroundColor: const Color(0xff565656),
type: BottomNavigationBarType.fixed,
showSelectedLabels: false,
showUnselectedLabels: false,
unselectedItemColor: Colors.white,
selectedItemColor: Colors.white,
onTap: onItemTap,
items: [
BottomNavigationBarItem(
icon: const Icon(Icons.home),
label: "Dashboard",
),
BottomNavigationBarItem(
icon: const Icon(Icons.car_repair),
label: "Target",
),
],
),
),
),
),
],
);
}
}
2 - dashboard.dart :
import 'package:flutter/material.dart';
class Dashboard extends StatelessWidget {
const Dashboard({super.key, required this.ref});
final Function(int)? ref ;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: Container(
width: 120,
height: 20,
color: Colors.blue,
child: InkResponse(
onTap: ()=>ref!(1),
child: Text('navigate to target'),
),
),
),
),
);
}
}
3 - target.dart:
import 'package:flutter/material.dart';
class Target extends StatelessWidget {
const Target({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Text('target'),
),
);
}
}
remove the import and re import for the right paths in your application file , but this is a work around and you should use the state management .

Get the rack to update after shuffle

In the example below, what is the best construct to use to get the rack to update after a shuffle?
It seems to me that when a StatefulWidget is created, with its corresponding State Object (SO), any method that you can call from elsewhere is a method that's attached to the widget itself (not to the SO).
But, to get the widget to update its display, the SetState() method can only go in the SO's method(s). So how does the method on the widget call a method on its SO?
import 'package:flutter/material.dart';
List<Block> g_blocks = [Block(Colors.red), Block(Colors.green), Block(Colors.blue)];
Rack g_rack = new Rack();
void main() => runApp(MyApp());
// This widget is the root of your application.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
fontFamily: 'PressStart',
),
home: MyHomeScreen(),
);
}
}
class MyHomeScreen extends StatefulWidget {
MyHomeScreen({Key key}) : super(key: key);
createState() => MyHomeScreenState();
}
class MyHomeScreenState extends State<MyHomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Center(child: Text('Thanks for your help')),
backgroundColor: Colors.pink,
),
body: Center(
child: g_rack,
),
bottomNavigationBar: SizedBox(
height: 100.0,
child: BottomNavigationBar(
currentIndex: 0,
iconSize: 48.0,
backgroundColor: Colors.lightBlue[100],
items: [
BottomNavigationBarItem(
label: 'Shuffle',
icon: Icon(Icons.home),
),
BottomNavigationBarItem(
label: 'Shuffle',
icon: Icon(Icons.home),
),
],
onTap: (int indexOfItem) {
setState(() {
g_blocks.shuffle;
rack.updateScreen(); // ** How to get the rack to update? **
});
},
),
),
);
} // build
} // End class MyHomeScreenState
class Rack extends StatefulWidget {
#override
_rackState createState() => _rackState();
}
class _rackState extends State<Rack> {
#override
Widget build(BuildContext context) {
return Container(
height: 150.0,
color: Colors.yellow[200],
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: g_blocks),
);
}
void updateRack(){
setState(() {
g_blocks.shuffle;
});
}
}
class Block extends StatelessWidget {
final Color color;
Block(this.color);
#override
Widget build(BuildContext context) {
return Container(height:50,width:50, color: color,);
}
}
Here is a solution where I try to decouple the State Management and Business Logic of the application from the User Interface.
I used the following packages:
freezed for the Domain Entities
hooks_riverpod for the State Management
1. Domain Layer: Entities
We need two Entities to model our Racks of Blocks.
Blocks are defined by their color.
Blocks have no business logic.
Racks are ordered lists of Blocks.
Racks can get shuffled.
Racks can be randomly created for a (random or given) number of Blocks
#freezed
abstract class Block with _$Block {
const factory Block({Color color}) = _Block;
}
#freezed
abstract class Rack implements _$Rack {
const factory Rack({List<Block> blocks}) = _Rack;
const Rack._();
static Rack create([int nbBlocks]) => Rack(
blocks: List.generate(
nbBlocks ?? 4 + random.nextInt(6),
(index) => Block(
color: Color(0x66000000 + random.nextInt(0xffffff)),
),
),
);
Rack get shuffled => Rack(blocks: blocks..shuffle());
}
We use the freeze package to have immutability and the precious copyWith method to manage our States.
2. Application Layer: State Management
We use Hooks Riverpod for our State Management. We just need one StateNotifier and its provider.
This StateNotifierProvider gives access to both the Rack State and the core functionalities that are deal() and shuffle().
class RackStateNotifier extends StateNotifier<Rack> {
static final provider =
StateNotifierProvider<RackStateNotifier>((ref) => RackStateNotifier());
RackStateNotifier([Rack state]) : super(state ?? Rack.create());
void shuffle() {
state = state.shuffled;
}
void deal() {
state = Rack.create();
}
}
3. Presentation Layer: User Interface
The User Interface is made of four Widgets:
AppWidget [StatelessWidget]
HomePage [HookWidget]
RackWidget [StatelessWidget]
BlockWidget [StatelessWidget]
As you see, the only Widget that really cares about the State of the Application is the HomePage.
3.1 AppWidget
class AppWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.amber,
accentColor: Colors.black87,
),
home: HomePage(),
);
}
}
3.2 HomePage
class HomePage extends HookWidget {
#override
Widget build(BuildContext context) {
final rack = useProvider(RackStateNotifier.provider.state);
return Scaffold(
appBar: AppBar(
title: Row(
children: const [
Icon(Icons.casino_outlined),
SizedBox(
width: 8.0,
),
Text('Rack Shuffler'),
],
),
),
body: Center(
child: RackWidget(rack: rack),
),
bottomNavigationBar: BottomAppBar(
color: Theme.of(context).primaryColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
icon: Icon(Icons.refresh),
iconSize: 48,
onPressed: () => context.read(RackStateNotifier.provider).deal(),
),
IconButton(
icon: Icon(Icons.shuffle),
iconSize: 48,
onPressed: () =>
context.read(RackStateNotifier.provider).shuffle(),
),
],
),
),
);
}
}
rack is provided by our StateNotifierProvider, in watch mode:
final rack = useProvider(RackStateNotifier.provider.state);
The Racks are dealt and shuffled using the same provider, in read mode:
...
context.read(RackStateNotifier.provider).deal(),
...
context.read(RackStateNotifier.provider).shuffle(),
...
3.3 RackWidget
class RackWidget extends StatelessWidget {
final Rack rack;
const RackWidget({Key key, this.rack}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: LayoutBuilder(
builder: (context, constraints) {
return Row(
children: rack.blocks
.map((block) => BlockWidget(
block: block,
size: constraints.biggest.width / rack.blocks.length))
.toList(),
);
},
),
);
}
}
Basic StatelessWidget. We use a LayoutBuilder to define the size of the BlockWidgets.
3.4 BlockWidget
class BlockWidget extends StatelessWidget {
final Block block;
final double size;
const BlockWidget({
Key key,
this.block,
this.size,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return SizedBox(
width: size,
height: size,
child: Padding(
padding: EdgeInsets.all(size / 10),
child: Container(
decoration: BoxDecoration(
color: block.color,
border: Border.all(color: Colors.black87, width: size / 20),
borderRadius: BorderRadius.circular(size / 15),
),
),
),
);
}
}
Another basic StatelessWidget.
Full Application Code
Just copy-paste the following to try it out.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
part '66053795.shuffle.freezed.dart';
Random random = Random();
void main() => runApp(ProviderScope(child: AppWidget()));
class AppWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.amber,
accentColor: Colors.black87,
),
home: HomePage(),
);
}
}
class HomePage extends HookWidget {
#override
Widget build(BuildContext context) {
final rack = useProvider(RackStateNotifier.provider.state);
return Scaffold(
appBar: AppBar(
title: Row(
children: const [
Icon(Icons.casino_outlined),
SizedBox(
width: 8.0,
),
Text('Rack Shuffler'),
],
),
),
body: Center(
child: RackWidget(rack: rack),
),
bottomNavigationBar: BottomAppBar(
color: Theme.of(context).primaryColor,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
icon: Icon(Icons.refresh),
iconSize: 48,
onPressed: () => context.read(RackStateNotifier.provider).deal(),
),
IconButton(
icon: Icon(Icons.shuffle),
iconSize: 48,
onPressed: () =>
context.read(RackStateNotifier.provider).shuffle(),
),
],
),
),
);
}
}
class RackWidget extends StatelessWidget {
final Rack rack;
const RackWidget({Key key, this.rack}) : super(key: key);
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: LayoutBuilder(
builder: (context, constraints) {
return Row(
children: rack.blocks
.map((block) => BlockWidget(
block: block,
size: constraints.biggest.width / rack.blocks.length))
.toList(),
);
},
),
);
}
}
class BlockWidget extends StatelessWidget {
final Block block;
final double size;
const BlockWidget({
Key key,
this.block,
this.size,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return SizedBox(
width: size,
height: size,
child: Padding(
padding: EdgeInsets.all(size / 10),
child: Container(
decoration: BoxDecoration(
color: block.color,
border: Border.all(color: Colors.black87, width: size / 20),
borderRadius: BorderRadius.circular(size / 15),
),
),
),
);
}
}
class RackStateNotifier extends StateNotifier<Rack> {
static final provider =
StateNotifierProvider<RackStateNotifier>((ref) => RackStateNotifier());
RackStateNotifier([Rack state]) : super(state ?? Rack.create());
void shuffle() {
state = state.shuffled;
}
void deal() {
state = Rack.create();
}
}
#freezed
abstract class Block with _$Block {
const factory Block({Color color}) = _Block;
}
#freezed
abstract class Rack implements _$Rack {
const factory Rack({List<Block> blocks}) = _Rack;
const Rack._();
static Rack create([int nbBlocks]) => Rack(
blocks: List.generate(
nbBlocks ?? 4 + random.nextInt(6),
(index) => Block(
color: Color(0x66000000 + random.nextInt(0xffffff)),
),
),
);
Rack get shuffled => Rack(blocks: blocks..shuffle());
}
Here is a solution using a GlobalKey.
It feels pretty inelegant. It surprises me that with the close relationship between the widget and its state object, there's no easy way for a widget's method to call a method on the SO. The "widget.blah" construct provides a way for the SO to access the widget's data, is there a reason for not having a similar "state.myMethod" construct?
Anyway, the following works:
import 'package:flutter/material.dart';
List<Block> g_blocks = [Block(Colors.red), Block(Colors.green),
Block(Colors.blue), Block(Colors.purple)];
GlobalKey g_key = GlobalKey();
Rack g_rack = new Rack(key: g_key);
void main() => runApp(MyApp());
// This widget is the root of your application.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
fontFamily: 'PressStart',
),
home: MyHomeScreen(),
);
}
}
class MyHomeScreen extends StatefulWidget {
MyHomeScreen({Key key}) : super(key: key);
createState() => MyHomeScreenState();
}
class MyHomeScreenState extends State<MyHomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Center(child: Text('Thanks for your help')),
backgroundColor: Colors.pink,
),
body: Center(
child: g_rack,
),
bottomNavigationBar: SizedBox(
height: 100.0,
child: BottomNavigationBar(
currentIndex: 0,
iconSize: 48.0,
backgroundColor: Colors.lightBlue[100],
items: [
BottomNavigationBarItem(
label: 'Shuffle',
icon: Icon(Icons.home),
),
BottomNavigationBarItem(
label: 'Shuffle',
icon: Icon(Icons.home),
),
],
onTap: (int index) {
g_blocks.shuffle();
g_key.currentState.setState(() {
});
}
),
),
);
} // build
} // End class MyHomeScreenState
class Rack extends StatefulWidget {
Rack({Key key}) : super(key: key);
#override
_rackState createState() => _rackState();
}
class _rackState extends State<Rack> {
#override
Widget build(BuildContext context) {
return Container(
height: 150.0,
color: Colors.yellow[200],
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: g_blocks),
);
}
void updateRack(){
setState(() {});
}
}
class Block extends StatelessWidget {
final Color color;
Block(this.color);
#override
Widget build(BuildContext context) {
return Container(height:50,width:50, color: color,);
}
}

Flutter googlemaps reloads everytime I change page in tabnavigator

Hello, I'm trying to use Flutter and the new googlemaps plugin in my application. My problem is that everytime I change page in the tabnav I it will reload googlemaps widget. I tried using AutomaticKeepAliveClientMixin but that didn't help. I will keep trying to figure this out, but help is appreciated or if somebody knows what I'm doing wrong? Thank you!
This is my code:
import 'package:flutter/material.dart';
import 'package:restapoints/pages/pages.dart';
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My Flutter App',
home: Home(),
);
}
}
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomeState();
}
}
class _HomeState extends State<Home> {
int _currentIndex = 0;
final List<Widget> _children = [
MapPage(),
QrPage(),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My Flutter App'),
),
body: _children[_currentIndex], // new
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped, // new
currentIndex: _currentIndex, // new
items: [
new BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
new BottomNavigationBarItem(
icon: Icon(Icons.mail),
title: Text('Messages'),
),
new BottomNavigationBarItem(
icon: Icon(Icons.person), title: Text('Profile'))
],
),
);
}
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
}
MapPage:
class MapPage extends StatefulWidget {
#override
_MapPageState createState() => _MapPageState();
}
class _MapPageState extends State<MapPage>
with AutomaticKeepAliveClientMixin<MapPage> {
#override
Widget build(BuildContext context) {
return Container(
///Getting size of the screen from [MediaQuery] inherited widget.
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Stack(
children: <Widget>[
GoogleMap(
onMapCreated: _onMapCreated,
options: GoogleMapOptions(
compassEnabled: true,
mapType: MapType.normal,
trackCameraPosition: true,
),
),
///If not enabled don't show ListView of places.
toggleListView
? Positioned(
top: MediaQuery.of(context).size.height / 2,
left: 10,
child: Container(
height: MediaQuery.of(context).size.height / 4,
width: MediaQuery.of(context).size.width,
child: ListView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.all(5.0),
children: _places.map((place) {
return _placeCard(place);
}).toList(),
)),
)
: Container()
],
),
);
#override
bool get wantKeepAlive => true;
}
Instead of making the page a StatefulWidget like MapPage, I added the Widget inside an IndexedStack to prevent rebuild of the page. Though the downside of this approach is that the page might take time to build if there's a lot of pages added on Stack.