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),
);
}
}
Related
I want to show an overlay on the whole app so I tried to insert an overlay entry on the context of MaterialApp (root widget) but the problem is I'm getting the null value on invoking the following method :
Overlay.of(context);
GetMaterialApp.router(
debugShowCheckedModeBanner: false,
theme: AppTheme.lightTheme,
scaffoldMessengerKey: Keys.scaffold,
scrollBehavior: MyCustomScrollBehavior(),
routeInformationParser: WebRoutes.goRouter.routeInformationParser,
routerDelegate: WebRoutes.goRouter.routerDelegate,
routeInformationProvider: WebRoutes.goRouter.routeInformationProvider,
builder: (context, child) {
WidgetsBinding.instance.addPostFrameCallback((_){
addOverlay(context);
});
return child;
}
void addOverlay(BuildContext context) {
print(Overlay.of(context));
return Overlay.of(context)?.insert(OverlayEntry(
builder: (context) {
return SomeWidget();
},
));
}
Is there any way to get the state of overlay using the context of this root widget as I want to show the overlay globally.
Thanks alot, I really appreciate that If someone helps me.
MaterialApp(
navigatorKey: getIt.get<NavigatorService>().navigatorKey,
theme: AppTheme.defaultTheme,
initialRoute: AppRoutes.splashScreen,
builder: (context, child) {
return Scaffold(
body: Stack(
children: [
child!,
Positioned(
top: 15,
child: Container(
color: Colors.red,
height: 50,
width: MediaQuery.of(context).size.width,
child: const Center(child: Text("HI I AM AN OVERLAY")),
),
),
],
),
);
},
onGenerateRoute: AppRoutes.onGenerateRoute,
),
You can achieve that by create a class responsible to display/remove the overlay, this class need receive a BuildContext when creating to be able to create an instance of Overlay.
Basically what you need to do are:
Create a class OverlayScreen that build the OverlayState && OverlayEntry (in this case the OverylayEntry will be a list of OverlayEntry since we might have more than one Overlay on the screen so we can remove all of them at once).
Create an instance of this class earlier in your app (e.g MyApp). In your case you'll need to call this inside Material.router...builder param.
Access this overlayScreen in your HomePage to display|removeAll overlays
Lets create our OverlayScreen
import 'package:flutter/material.dart';
class OverlayScreen {
/// Create an Overlay on the screen
/// Declared [overlayEntrys] as List<OverlayEntry> because we might have
/// more than one Overlay on the screen, so we keep it on a list and remove all at once
BuildContext _context;
OverlayState? overlayState;
List<OverlayEntry>? overlayEntrys;
void closeAll() {
for (final overlay in overlayEntrys ?? <OverlayEntry>[]) {
overlay.remove();
}
overlayEntrys?.clear();
}
void show() {
overlayEntrys?.add(
OverlayEntry(
builder: (context) {
return _buildOverlayWidget();
},
),
);
overlayState?.insert(overlayEntrys!.last);
}
OverlayScreen._create(this._context) {
overlayState = Overlay.of(_context);
overlayEntrys = [];
}
factory OverlayScreen.of(BuildContext context) {
return OverlayScreen._create(context);
}
Widget _buildOverlayWidget() {
return Positioned(
top: 20,
left: 20,
right: 20,
child: Container(
width: 300,
color: Colors.black,
height: 300,
child: const Text("MY CHAT"),
),
);
}
}
Now lets create an instance on MyApp
// Need to have it global to be able to access everywhere
OverlayScreen? overlayScreen;
void main() {
runApp(
const MyApp(),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: const HomePage(),
builder: (context, child) {
return Overlay(
initialEntries: [
OverlayEntry(
builder: (context) {
// Create an instance of `OverlayScreen` to be accessed globally
overlayScreen = OverlayScreen.of(context);
return child ?? const SizedBox();
},
),
],
);
},
);
}
}
To finalise lets create our HomePage and access our overlayScreen there there
import 'package:flutter/material.dart';
import 'package:overlay_all_app/src/overlay_screen.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
#override
Widget build(BuildContext context) {
// Create an instance of OverlayScreen
final overlayScreen = OverlayScreen.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
onPressed: () {
// display the overlay
overlayScreen.show();
},
child: const Text('Display Overlay'),
),
const SizedBox(height: 30),
TextButton(
onPressed: () {
// Call your next screen here
},
child: const Text('Go to next page'),
),
const SizedBox(height: 30),
TextButton(
onPressed: () {
// removed all overlays on the screen
overlayScreen.closeAll();
},
child: const Text('Close Overlay'),
),
],
),
),
);
}
}
That's it. You can use this class OverlayScreen to show/removeAll wherever you want.
I created a PR with sample code, check it out https://github.com/antonio-nicolau/flutter-working-with-overlay
import 'package:flutter/material.dart';
import 'package:get/get_navigation/src/root/get_material_app.dart';
import 'package:go_router/go_router.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(App2());
}
class App2 extends StatelessWidget {
App2({super.key});
final _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const OverlayWrapper(),
),
],
);
#override
Widget build(BuildContext context) {
return GetMaterialApp.router(
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate,
routeInformationProvider: _router.routeInformationProvider,
);
}
}
class OverlayWrapper extends StatefulWidget {
const OverlayWrapper({Key? key}) : super(key: key);
#override
State<OverlayWrapper> createState() => _OverlayWrapperState();
}
class _OverlayWrapperState extends State<OverlayWrapper> {
#override
void initState() {
super.initState();
}
showOverLay() {
OverlayEntry overlayEntry = OverlayEntry(
builder: (context) => Container(
color: Colors.red,
child: const Text('data'),
),
);
Overlay.of(context).insert(overlayEntry);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
showOverLay();
},
child: const Text(
'ShowOverlay',
style: TextStyle(),
),
),
),
);
}
}
I changed it to stateful and used return dialogue but still nothing.
void main() {
runApp(
MyApp(),
);
}
class MyApp extends StatefulWidget {
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
createAlertDialogue(BuildContext context) {
TextEditingController customeController = TextEditingController();
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(" Your NAme"),
content: TextField(
controller: customeController,
),
actions: <Widget>[
MaterialButton(
elevation: 5.0,
child: Text('submit'),
onPressed: () {},
)
],
);
});
}
Widget _buildForgotPasswordBtn(BuildContext ctx) {
return Container(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () {
setState(() {
createAlertDialogue(ctx);
});
},
child: Text(
'forgot password',
style: kLabelStyle,
),
),
);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Color.fromARGB(206, 5, 42, 146),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_buildForgotPasswordBtn(context),
],
),
),
),
);
}
}
I used setState and even put ctx in createalertdialogue because it didn't work until I changed from context to this.
I don't get any errors but I get nothing when I press the forgot password button on the emulator.
The button is there and can be pressed with no errors but still nothing appears.
i fixed it nevermind sorry the answer was changing the navigation method to Navigator.pushNamed
I Just slightly edited your code
Wrap your runApp with MaterialApp
from
void main() {
runApp(
MyApp(),
);
}
to
void main() {
runApp(
MaterialApp(
home: MyApp(),
),
);
}
your full code
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: MyApp(),
),
);
}
class MyApp extends StatefulWidget {
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
createAlertDialogue(BuildContext context) {
TextEditingController customeController = TextEditingController();
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(" Your NAme"),
content: TextField(
controller: customeController,
),
actions: <Widget>[
MaterialButton(
elevation: 5.0,
child: Text('submit'),
onPressed: () {},
)
],
);
});
}
Widget _buildForgotPasswordBtn(BuildContext ctx) {
return Container(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () {
setState(() {
createAlertDialogue(ctx);
});
},
child: Text(
'forgot password',
),
),
);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Color.fromARGB(206, 5, 42, 146),
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_buildForgotPasswordBtn(context),
],
),
),
),
);
}
}
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"),
);
}
}
i am trying to go on another page using navigation, but i am getting error;
Navigator operation requested with a context that does not include a
Navigator.
i am just trying to move on next page, i followed flutter documentations for this stateless widget but how to do with state full widget.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State createState() => new MyApp1();
}
class MyApp1 extends State<MyApp> {
List<Widget> _listSection = [];
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Share IDEASS',
initialRoute: '/',
routes: {
'/second': (context) => SecondScreen(),
},
home: Scaffold(
appBar: AppBar(
title: Text('IDEAS'),
),
body: Container(
child: Stack(
children: [
floatingButton(),
],
),
),
),
);
}
Widget floatingButton() {
return Container(
padding: const EdgeInsets.all(30),
alignment: Alignment.bottomRight,
child: FloatingActionButton(
onPressed: () {
Navigator.pushNamed(context, "/SecondScreen");
},
child: Text("+"),
backgroundColor: Colors.blue,
),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
You should use the named route you created.
Widget floatingButton(BuildContext context) { // added context as a parameter
return Container(
padding: const EdgeInsets.all(30),
alignment: Alignment.bottomRight,
child: FloatingActionButton(
onPressed: () {
Navigator.pushNamed(context, "/second"); // Changed this to use the named route
},
child: Text("+"),
backgroundColor: Colors.blue,
),
);
}
}
then use the following
body: Container(
child: Stack(
children: [
floatingButton(context),
],
),
),
The situation here is that the floatingButton() uses a context with the navigator to push the given page route. But the context used is provided in the parent Widget(MaterialApp) it self, which doesn't include a Navigator, hence the error.
So, Try this approach:
Separate the Home widget from the MaterialApp, like below:
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Share IDEASS',
initialRoute: '/',
routes: {
'/second': (context) => SecondScreen(),
},
home: HomePage(),
);
Create a stateless widget containing the Scaffold:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('IDEAS'),
),
body: Container(
child: Stack(
children: [
floatingButton(),
],
),
),
);
}
}
Hope it helps. Let me know if this doesn't work.
You have made two mistakes because of which your code is not working:
You have used wrong route name. Replace /SecondScreen with /second
You have used wrong context. You can get Navigator only if your widget has MaterialApp as it's parent and here you are using context of MyApp1 so it is not working.
Following is a working code for your reference.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State createState() => new MyApp1();
}
class MyApp1 extends State<MyApp> {
List<Widget> _listSection = [];
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Share IDEASS',
initialRoute: '/',
routes: {
'/second': (context) => SecondScreen(),
},
home: AppContent(),
);
}
}
class AppContent extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('IDEAS'),
),
body: Container(
child: Stack(
children: [
floatingButton(context),
],
),
),
);
}
Widget floatingButton(BuildContext context) {
return Container(
padding: const EdgeInsets.all(30),
alignment: Alignment.bottomRight,
child: FloatingActionButton(
onPressed: () {
Navigator.pushNamed(context, "/second");
},
child: Text("+"),
backgroundColor: Colors.blue,
),
);
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
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(...
}