Expandable button overflowing top of container - flutter

I'm trying to make an expandable button, a bit like the expandable fab, except it's not a fab as it is not floating. This is the expandable fab for perspective:
What I'm trying to achieve though is to have a self contained button that expands above it with a menu. Self contained is in bold because I'd like the widget to be used easily without having to modify the parents structure.
So if you copy paste the code below in dartpad you'll see a yellow bar at the bottom. However if you uncomment the lines which are commented, which represents the menu expanding, you'll see that the bottom bar is pushed to the top.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(
children: [
Expanded(child: Container(color: Colors.purple)),
MyWidget(),
]
),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SizedOverflowBox(
size: Size(double.infinity, 100),
child: Stack(
children: [
Container(color: Colors.amber, height: 100),
// Transform.translate(
// offset: Offset(0, -400),
// child: Container(color: Colors.lightBlue, height: 400, width: 80),
// ),
]
)
);
}
}
So my questions are:
How do I achieve the required result where the bottom bar does not move and a menu above it (light blue container); modifying only MyWidget and not MyApp ?
Why in the current code the bar is pushed above ?

Overlay and OverlayEntry can help to achieve this:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(
children: [
Expanded(child: Container(color: Colors.purple)),
MyWidget(),
]
),
),
),
);
}
}
class MyWidget extends StatelessWidget {
OverlayEntry? _overlayEntry;
_hideMenu() {
_overlayEntry?.remove();
}
_showMenu(BuildContext context) {
final overlay = Overlay.of(context);
_overlayEntry = OverlayEntry(
builder: (ctx) => Stack(
children: [
GestureDetector(
onTap: () => _hideMenu(),
child: Container(color: Colors.grey.withAlpha(100)),
),
Positioned(
bottom: 100,
left: 50,
child: Container(color: Colors.pink, height: 200, width: 50,),
),
],
)
);
overlay?.insert(_overlayEntry!);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => _showMenu(context),
child: Container(color: Colors.amber, height: 100)
);
}
}

Try this, run this code in dartpad.
It contains one parent, three child which can be called using the menu buttons,
The FloatingActionButton.extended used in this code can be replaced by any custom Widget, you can give onTap methods for clicks,
I have used simple widgets, Let me know wether you were looking for something like that, or something different.
import 'package:flutter/material.dart';
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(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'I am Parent'),
);
}
}
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> {
bool showButtons = false;
var index = 0;
List<Widget> childList = [Child1(), Child2(), Child3()];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: childList[index],
),
floatingActionButton: Column(
mainAxisSize: MainAxisSize.min,
children: [
Visibility(
visible: showButtons,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
FloatingActionButton.extended(
heroTag: 'btn1',
onPressed: () {
setState(() {
index = 0;
});
},
label: Text(
"Sub Btn1",
style: TextStyle(color: Colors.black),
),
elevation: 3,
backgroundColor: Colors.yellowAccent,
),
Padding(
padding: EdgeInsets.only(top: 3),
child: FloatingActionButton.extended(
heroTag: 'btn1',
onPressed: () {
setState(() {
index = 1;
});
},
label: Text(
"Sub Btn2",
style: TextStyle(color: Colors.black),
),
elevation: 3,
backgroundColor: Colors.yellowAccent,
)),
Padding(
padding: EdgeInsets.only(top: 3),
child: FloatingActionButton.extended(
heroTag: 'btn3',
onPressed: () {
setState(() {
index = 2;
});
},
label: Text(
"Sub Btn3",
style: TextStyle(color: Colors.black),
),
elevation: 3,
backgroundColor: Colors.yellowAccent,
))
],
),
),
RaisedButton(
onPressed: () {
setState(() {
showButtons = !showButtons;
});
},
child: Text("Self Contained"),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16)),
color: Colors.yellow,
),
],
) // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class Child1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Text("I am Child 1"),
);
}
}
class Child2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Text("I am Child 2"),
);
}
}
class Child3 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Text("I am Child 3"),
);
}
}

Related

GestureDetector on tap is not triggring on alll the screen

i am learning flutter and i just created an app that will update a counter variable when the user clicks on any part of the screen except the AppBar. However, the GestureDetector does not detect any clicks on the4 screen except for those on the text itself
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text("i am a title"),
backgroundColor: Colors.teal,
leading: const Icon(
Icons.home,
color: Colors.white,
),
),
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [
Expanded(
child: Clicks(),
),
],
),
),
),
);
}
}
class Clicks extends StatefulWidget {
const Clicks({super.key});
#override
State<Clicks> createState() => _ClicksState();
}
class _ClicksState extends State<Clicks> {
int count = 0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => setState(
() {
count++;
},
),
child: Center(
child: Text("total number of clicking on scren is $count "),
),
);
}
}
The GestureDetector will detect any gestures for it's child - in this case the Text widget.
If you want to detect everything, wrap the entrie Scaffold with a GestureDector.
In this case, you'll have to refactor your code:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Clicks();
}
}
class Clicks extends StatefulWidget {
const Clicks({super.key});
#override
State<Clicks> createState() => _ClicksState();
}
class _ClicksState extends State<Clicks> {
int count = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text("i am a title"),
backgroundColor: Colors.teal,
leading: const Icon(
Icons.home,
color: Colors.white,
),
),
body: GestureDetector(
onTap: () {
setState(() {
count++;
});
},
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: Text("clicked a total of $count times"),
),
],
),
),
),
),
);
}
}
Try with below changes :
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => setState(
() {
count++;
},
),
child: Center(
child: Text("total number of clicking on scren is $count "),
),
);

Creating a global floating button in flutter

Is it possible to have floating button visible throughout the life cycle of the app on top of all pages? I know that with Scaffold I can have it but it only works for that page and i'll lose it once I push a new page on the navigator stack.
Yes, OverlayEntry is made for this purpose.
To insert it, you can do something like this:
ElevatedButton(
child: Text("Overlay Test"),
onPressed: () {
final entry = OverlayEntry(
builder: (context) => Container(
color: Colors.blue,
),
);
Overlay.of(context)?.insert(entry);
},
)
If you want to remove it later, you can save the entry variable and then call entry.remove() when needed.
Full example:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
OverlayEntry? _entry;
double _left = 50;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("OverlayEntry Demo"),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
child: Text("Add OverlayEntry"),
onPressed: () {
_entry = OverlayEntry(
builder: (context) {
print("build");
return Positioned(
left: _left,
top: 200,
child: Container(
width: 150,
height: 150,
color: Colors.grey,
),
);
},
);
Overlay.of(context)?.insert(_entry!);
},
),
ElevatedButton(
child: Text("Move it"),
onPressed: () {
_left += 10;
_entry?.markNeedsBuild();
},
),
ElevatedButton(
child: Text("Remove it"),
onPressed: () => _entry?.remove(),
),
],
),
),
);
}
}
One option is to use the builder of the MaterialApp to create a Stack with your Button on top:
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
home: TestPage(),
initialRoute: "/test",
builder: (context, child) {
return Scaffold(
body: Stack(
children: [
child!,
Positioned(
left: 0,
bottom: 0,
child: **your button here**,
),
],
),
);
},
routes: routes(context),
);
}
}

how to get a grid element accessible in flutter by clicking on it?

is there anyone who can help me ?
I am currently on a project where i want to visualize pathfinding-algorithms by using flutter (i want to use it as app later on).
My Problem:
I have a gridPaper and it's perfectly formatted for my needs... but how can i make the single elements in it accessible by clicking on them ?
I want to create a 'wall' between the start- and endnode to make it harder for the pathfinding-algorithm. (if that makes sense)
But at first i need to create a start- end endnode as well.
Here is what i have so far:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final appTitle = 'Path Finder';
final Color gridColor = Colors.lightBlue[100];
#override
Widget build(BuildContext context) {
return MaterialApp(
title: appTitle,
home: MyHomePage(title: appTitle),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: GridPaper(
child: Container(),
color: Colors.lightBlue[100],
interval: 20,
divisions: 1,
subdivisions: 1,
),
drawer: Drawer(
// Add a ListView to the drawer. This ensures the user can scroll
// through the options in the drawer if there isn't enough vertical
// space to fit everything.
child: ListView(
// Important: Remove any padding from the ListView.
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Startpunkt'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
ListTile(
title: Text('Ziel'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
],
),
),
);
}
}
LG Robsen
Since your GridPaper is defined with intervals of 20, it will be quite easy to use the localPosition of the details of an onTapDown callback provided by a GestureDetector on the whole GridPaper:
Full source code
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final appTitle = 'Path Finder';
final Color gridColor = Colors.lightBlue[100];
#override
Widget build(BuildContext context) {
return MaterialApp(
title: appTitle,
home: MyHomePage(title: appTitle),
);
}
}
class MyHomePage extends HookWidget {
final double cellSize = 20.0;
final String title;
MyHomePage({
Key key,
this.title,
}) : super(key: key);
#override
Widget build(BuildContext context) {
final _activated = useState<List<Offset>>([]);
void _toggle(Offset offset) {
if (!_activated.value.remove(offset)) _activated.value.add(offset);
_activated.value = [..._activated.value];
}
return Scaffold(
appBar: AppBar(title: Text(title)),
body: GestureDetector(
onTapDown: (details) => _toggle(details.localPosition ~/ cellSize),
child: GridPaper(
child: Stack(
children: [
Container(color: Colors.white),
..._activated.value.map((offset) {
print('OFFSET: $offset');
return Positioned(
left: offset.dx * cellSize,
top: offset.dy * cellSize,
width: cellSize,
height: cellSize,
child: ColoredBox(color: Colors.green.shade200),
);
}).toList(),
],
),
color: Colors.lightBlue[100],
interval: cellSize,
divisions: 1,
subdivisions: 1,
),
),
drawer: Drawer(
// Add a ListView to the drawer. This ensures the user can scroll
// through the options in the drawer if there isn't enough vertical
// space to fit everything.
child: ListView(
// Important: Remove any padding from the ListView.
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Startpunkt'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
ListTile(
title: Text('Ziel'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
],
),
),
);
}
}

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's AutomaticKeepAliveClientMixin doesn't keep the page state after navigator.push

was testing AutomaticKeepAliveClientMixin and run into an issue,
page loses state after navigator.push
anyone knows this issue? any workarounds? be glad for any info, cheers
my goal is to keep the page state
steps to reproduce: open app click PageOne's push-button then go back swipe right and left and the page loses state
image
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
initialIndex: 0,
length: 2,
child: Scaffold(
body: TabBarView(
children: <Widget>[Page1(), Page2()],
),
bottomNavigationBar: Material(
child: TabBar(
labelColor: Colors.black,
tabs: <Widget>[
Tab(
icon: Icon(Icons.check),
),
Tab(
icon: Icon(Icons.check),
),
],
),
),
),
),
);
}
}
class Page1 extends StatefulWidget {
#override
Page1State createState() {
return new Page1State();
}
}
class Page1State extends State<Page1> with AutomaticKeepAliveClientMixin {
#override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
Container(
height: 300,
color: Colors.orange,
),
Container(
height: 300,
color: Colors.pink,
),
Container(
height: 300,
color: Colors.yellow,
child: Center(
child: Container(height: 26,
child: MaterialButton(
color: Colors.blue,
child:
Text('clicking this and back then swipe => page loses state'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => PushedPage()),
);
}),
),
),
),
],
);
}
#override
bool get wantKeepAlive => true;
}
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(height: 300, color: Colors.orange);
}
}
class PushedPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.blue,
),
);
}
}
From the documentation on AutomaticKeepAliveClientMixin:
A mixin with convenience methods for clients of
[AutomaticKeepAlive]. Used with [State] subclasses.
Subclasses must implement [wantKeepAlive], and their [build]
methods must call super.build (the return value will always return
null, and should be ignored).
So in your code, before you return the ListView just call super.build:
Widget build(BuildContext context) {
super.build(context);
return ListView(...
}