How to navigate between two screens in flutter and show keyboard? - flutter

I have some next (simplified) widget-tree structures:
- home (stack of widgets)
- page container
- editText field
As you can see, edit text will be always on top of the pages, which I will swap with Navigator.of(context) transactions.
The problem: when I focus editText the keyboard appears. When user starts typing, at some moment, I need to push another screen inside the page container. And when I use Navigator.of() - editText is unfocused and the keyboard goes down.
Whats needed: I need somehow to do transition (push/pop/replace) in page container with the Navigator and keep keyboard still up (keep editText focused).
Do you have any knowledge of how to do that?

Your use case looks really weird but I suppose you have a good reason to follow this setup.
Anyway the issue is that when Navigator.push is called, the TextField is unfocused. This is not a bug but rather an intended feature which (I think) is here to prevent a keyboard to follow you to the next screen where no TextField would be present. However I understand that this is an issue for your use case.
My solution (not the prettiest I would admit, but the only thing I could have working) is to force the focus back to the TextField manually. You need two component:
A FocusNode to associate to your TextField to listen to check if your TextField changes from focus to unfocused. If the focusOnNextUnfocus is set, it should re-request the focus after an unfocus
focusOnNextUnfocus a bool to set to true just before pushing, and which will be set to false once the focus has been requested again.
Here is the adaptation of the code you provided:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
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> {
/// Handles the focus state of our text field
final focusNode = FocusNode();
/// A bool to keep track on whether we should refocus after
/// unfocus. This will be set to `true` before pushing
var focusOnNextUnfocus = false;
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
void initState() {
focusNode.addListener(() {
// If we no longer have the focus and should refocus, we refocus
if (!focusNode.hasFocus && focusOnNextUnfocus) {
focusNode.requestFocus();
focusOnNextUnfocus = false; // Reset to false once it has been processed
}
});
super.initState();
}
#override
void dispose() {
focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
late BuildContext anotherContext;
final edit = TextField(
focusNode: focusNode,
onChanged: (String value) async {
if (value.length > 4) {
// Pop does not unfocus so no worries here
Navigator.pop(anotherContext);
} else if (value.length > 3) {
// Push DOES unfocus, so we need to set focusOnNextUnfocus to true
focusOnNextUnfocus = true;
Navigator.of(anotherContext).push(
MaterialPageRoute(builder: (_) {
return Builder(builder: (context) {
return Another();
});
}),
);
}
},
);
final scaffold = MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Builder(
builder: (context) {
anotherContext = context;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
),
);
return Scaffold(
body: Stack(
children: [
Positioned(child: scaffold),
Align(
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: edit,
),
),
],
),
);
}
}
class Another extends StatelessWidget {
const Another({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
);
}
}

Related

How to manipulate the ScrollController in Flutter's sliding up panel plugin?

I'm using Flutter's sliding_up_panel plugin.
I want to scroll the panel to the top when a new item is selected from my app drawer. Presently selecting a new item closes the panel and refreshes the panel content. Then opens it to a 200px peak, but it doesn't reset the panel's Scroll location to the top.
I've been going around in circles trying the same solutions in slightly different ways and getting nowhere.
What I've tried:
I have global
PanelController slidingPanelController = new PanelController();
ScrollController slideUpPanelScrollController = new ScrollController();
I tried attaching my global slideUpPanelScrollController to my panel's listview, but when swiping up the panel's ListView it simultaneously starts closing the whole panel. If you were scrolling up to read the content you'd skimmed, well, you're not able to because it's disappearing.
Preventing this bug is easy, you do it the canonical way from the plugin's examples, pass the ScrollController through from SlidingPanel and therefore create a local ScrollController in the panel's Listview.
panelBuilder: (slideUpPanelScrollController) => _scrollingList(presentLocation, slideUpPanelScrollController)
The problem then is, you can't scroll the panel on new App drawer selections, because the controller is now local.
I tried putting a listener on the local listview ScrollController, _slideUpPanelScrollController and testing for panelController.close():
if(slidingPanelController.isPanelClosed) _slideUpPanelScrollController.jumpTo(0);
But the listener blocked the panel from swiping, swipe events fired but the panel didn't swipe, or was extremely reluctant too.
Having freshly selected content open in the panel displaying content halfway down the ListView is a glitchy user experience. I would love some ideas or better solutions.
I need it so when the panel is closed, I can
slideUpPanelScrollController.jumpTo(0);
I need the global controller to attach to the panel ListView's local controller, or I need a way to access the local controller to fire its Scroll from outwith my _scrollingList() function.
Here's the panel Widget:
SlidingUpPanel(
key: Key("slidingUpPanelKey"),
borderRadius: slidingPanelBorderRadius,
parallaxEnabled: false,
controller: slidingPanelController,
isDraggable: isDraggableBool,
onPanelOpened: () {
},
onPanelSlide: (value) {
if (value >= 0.98)
setState(() {
slidingPanelBorderRadius =
BorderRadius.vertical(top: Radius.circular(16));
});
},
onPanelClosed: () async {
setState(() {
listViewScrollingOff = true;
});
imageZoomController.value =
Matrix4.identity(); // so next Panel doesn't have zoomed in image
slidingPanelBorderRadius =
BorderRadius.vertical(top: Radius.circular(16));
},
minHeight: panelMinHeight,
maxHeight:
MediaQuery.of(context).size.height - AppBar().preferredSize.height,
panelBuilder: (slideUpPanelScrollController) => _scrollingList(presentLocation, slideUpPanelScrollController),
body: ...
Here's the _scrollingList Widget:
Widget _scrollingList(LocationDetails presentLocation, ScrollController _slideUpPanelScrollController ) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView(
controller: _slideUpPanelScrollController,
physics: listViewScrollingOff
? const NeverScrollableScrollPhysics()
: const AlwaysScrollableScrollPhysics(),
key: Key("scrollingPanelListView"),
children: [
This is my onTap from my Drawer ListView item:
onTap: () {
if(slidingPanelController.isPanelShown) {
//slideUpPanelScrollController.jumpTo(0);
slidingPanelController.close();
}
Love help! Below is I think a minimum viable problem. I wrote it in Dartpad, but sharing from Dartpad is nontrivial, so I've copied and pasted it here. Dartpad doesn't support the plugin anyway so it's not like you could tweak it there.
import 'package:flutter/material.dart';
import 'package:sliding_up_panel/sliding_up_panel.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
PanelController slidingPanelController = new PanelController();
ScrollController slideUpPanelScrollController = new ScrollController();
final String title = "sliding panel";
String panelContent = "";
String stupidText = "";
String stupidText2 = ""
int panelMinHeight = 0;
int teaserPanelHeight = 77;
bool listViewScrollingOff = false;
initState() {
super.initState();
for(int i = 0; i < 500; i++) {
stupidText += "More stupid text. ";
}
for(int i = 0; i < 500; i++) {
stupidText2 += "More dumb, dumbest text. ";
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(title: Text(title)),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
decoration: BoxDecoration(
color: Colors.blue,
),
child: Text('Drawer Header'),
),
ListTile(
title: const Text('Item 1'),
onTap: () {
if(slidingPanelController.isPanelShown) {
print('attempting to scroll to top and close panel');
//slideUpPanelScrollController.jumpTo(0);
slidingPanelController.close();
}
Navigator.of(context).pop();
setState() {
panelContent = stupidText1;
panelMinHeight = teaserPanelHeight;
}
},
),
ListTile(
title: const Text('Item 2'),
onTap: () {
if(slidingPanelController.isPanelShown) {
//slideUpPanelScrollController.jumpTo(0);
slidingPanelController.close();
}
Navigator.of(context).pop();
setState() {
panelContent = stupidText2;
panelMinHeight = teaserPanelHeight;
}
}
),
],
),
),
body: SlidingUpPanel(
key: Key("slidingUpPanelKey"),
borderRadius: 8,
parallaxEnabled: false,
controller: slidingPanelController,
isDraggable: true,
onPanelOpened: () async {
setState(() {
listViewScrollingOff = false;
panelMinHeight = 0;
animatedMarkerMap;
//slideUpPanelScrollController.jumpTo(0);
});
},
onPanelSlide: (value) {
print("onPanelSlide: attempting to scroll panel");
},
onPanelClosed: () async {
setState(() {
//slideUpPanelScrollController.jumpTo(0);
listViewScrollingOff = true;
});
},
minHeight: panelMinHeight,
maxHeight:
MediaQuery.of(context).size.height - AppBar().preferredSize.height,
// TODO BUG
// SAM, IF I USE PANELBUILDER's ScrollController attached to the panel's ListView, then, when closing, the ListView will move to the top first, then the panel closes,
// however ListView's controller is set to a globalController, this causes a bug when closing the panel, but means you can open/peek the panel from the App drawer,
panelBuilder: (slideUpPanelScrollController) => _scrollingList(panelContent, slideUpPanelScrollController),
body: Center(
child: Text(
'Hello, World!',
style: Theme.of(context).textTheme.headline4,
),
),
),
),
);
}
Widget _scrollingList(String panelContent, ScrollController _slideUpPanelScrollController ) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView(
controller: _slideUpPanelScrollController,
physics: listViewScrollingOff
? const NeverScrollableScrollPhysics()
: const AlwaysScrollableScrollPhysics(),
key: Key("scrollingPanelListView"),
children: [Text(panelContent)])));
}
}
OK, so the problem became that when I closed the sliding panel 'naturally', by scrolling back up the panel to it top then sliding the panel down, well both things happened at once.
I've found how to solve this, I need to set SlidingUpPanel's isDraggable property to false, till the user has scrolled to the top of the panel.
Like so...
#override
void initState() {
super.initState();
slideUpPanelScrollController.addListener(() {
if(slideUpPanelScrollController.offset == 0) {
setState(() {
isDraggableBool = true;
});
}
});
}
The shortfall of this approach is the listener is running its test whenever the panel is Scrolled, could it jank the scroll? Is there a better/clearer/more performant way?
For completion I amended setScrollBehaviour to this:
void setScrollBehavior(bool _canScroll, {resetPos = false}) {
setState(() {
canScroll = _canScroll;
isDraggableBool = !_canScroll;
if (resetPos) {
slideUpPanelScrollController.jumpTo(0);
isDraggableBool = true;
}
});
}
So when the user can scroll they can't drag.
When the panel closes, resetPos == true therefore the panel scrolls to the top AND it can be dragged (slid) once more.
==== UPDATE ====
you can clone panel builder scrolling behavior and use the controller freely elsewhere. that way, you don't have to worry much about listview scroll physic or panel states.
did some cleanup, added comments, and fixed the tap/scrolling issues.
The only drawback of this method is that the user has to scroll back to the top to close the panel (you can always wrap your body with a gesture and call resetPanel on tap if needed)
import 'package:flutter/material.dart';
import 'package:sliding_up_panel/sliding_up_panel.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final PanelController panelController = PanelController();
ScrollController? _scrollController;
/// reset the content position of your listview
void resetScrollBehavior() {
// We make sure that our scroll exist and it is attached before reset
if (_scrollController != null && _scrollController!.hasClients) {
_scrollController!.jumpTo(0);
}
}
/// close the panel and reset the scroll behavior
void resetPanel() {
// We make sure our panel is attached and open before executing
if (panelController.isAttached && panelController.isPanelOpen) {
panelController.close();
// Remove this line if you wish to not reset the scrollExtent on panel reset.
resetScrollBehavior();
}
}
void onDrawerItemTap() {
// Reset the panel and pop the screen
resetPanel();
Navigator.of(context).pop();
}
#override
void dispose() {
// we make sure to dispose our controller(s)
_scrollController?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
child: Text('Drawer Header'),
),
ListTile(
title: const Text('Item 1'),
onTap: () {
onDrawerItemTap();
},
),
ListTile(
title: const Text('Item 2'),
onTap: () {
onDrawerItemTap();
},
),
],
),
),
body: SlidingUpPanel(
controller: panelController,
borderRadius: BorderRadius.circular(8),
minHeight: 80,
maxHeight:
MediaQuery.of(context).size.height - AppBar().preferredSize.height,
panelBuilder: (ScrollController sc) {
// ! So we can use local scrollcontroller outside the panel builder
_scrollController ??= sc;
return ScrollingList(
scrollController: _scrollController!,
);
},
body: Center(
child: Text(
'Hello, World!',
style: Theme.of(context).textTheme.headline4,
),
),
),
);
}
}
class ScrollingList extends StatefulWidget {
const ScrollingList({
Key? key,
required this.scrollController,
}) : super(key: key);
final ScrollController scrollController;
#override
_ScrollingListState createState() => _ScrollingListState();
}
class _ScrollingListState extends State<ScrollingList> {
#override
Widget build(BuildContext context) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView(
controller: widget.scrollController,
children: List.generate(200, (index) => Text('Text #$index')),
),
),
);
}
}
=== OLD ===
Running some test, it seems that panelController.close();
also triggers onPanelClosed();
so you can take advantage of that to handle your scrolling behaviors.
The following sample demonstrates how you can reset your panel and/or listview by tapping on a drawer item.
import 'package:flutter/material.dart';
import 'package:sliding_up_panel/sliding_up_panel.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
PanelController panelController = PanelController();
ScrollController scrollController = ScrollController();
bool canScroll = false;
void onDrawerItemTap() {
if (panelController.isAttached && panelController.isPanelOpen) {
panelController.close();
}
Navigator.of(context).pop();
}
void setScrollBehavior(bool _canScroll, {resetPos = false}) {
setState(() {
canScroll = _canScroll;
if (resetPos) {
scrollController.jumpTo(0);
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
child: Text('Drawer Header'),
),
ListTile(
title: const Text('Item 1'),
onTap: () {
onDrawerItemTap();
},
),
ListTile(
title: const Text('Item 2'),
onTap: () {
onDrawerItemTap();
},
),
],
),
),
body: SlidingUpPanel(
controller: panelController,
borderRadius: BorderRadius.circular(8),
parallaxEnabled: false,
isDraggable: true,
onPanelOpened: () {
setScrollBehavior(true);
},
onPanelSlide: (_) {
// panel slide
},
onPanelClosed: () {
setScrollBehavior(false, resetPos: true);
},
minHeight: 80,
maxHeight:
MediaQuery.of(context).size.height - AppBar().preferredSize.height,
panelBuilder: (_) => ScrollingList(
canScroll: canScroll,
scrollController: scrollController,
),
body: Center(
child: Text(
'Hello, World!',
style: Theme.of(context).textTheme.headline4,
),
),
),
);
}
}
class ScrollingList extends StatefulWidget {
const ScrollingList({
Key? key,
this.canScroll = false,
required this.scrollController,
}) : super(key: key);
final bool canScroll;
final ScrollController scrollController;
#override
_ScrollingListState createState() => _ScrollingListState();
}
class _ScrollingListState extends State<ScrollingList> {
#override
Widget build(BuildContext context) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 600),
child: ListView(
controller: widget.scrollController,
physics: !widget.canScroll
? const NeverScrollableScrollPhysics()
: const AlwaysScrollableScrollPhysics(),
children: List.generate(200, (index) => Text('Text #$index')),
),
),
);
}
}
ListView might be the issue here. You could try to jump to the top before closing the panel instead of after. You could also try to provide a key to your ListView so it forces flutter to re-render. Eventually, if it doesn't work wrap the body of the slide panel inside a SingleChildScrollview and disable ListView scrolling using physics. It's a bit difficult to visualize a fix though. If you can provide a link to DartPad it would be much easier.

Flutter. How to scan a barcode without UI

How can you scan a 2D barcode using a flutter without the camera's UI? I don't need to see the camera preview. I just want to click on the "Scan" button, and the camera started looking for a barcode, but it did not display what the phone's camera sees in UI.
You could look into using the qr_code_scanner package (pub link). Instead of opening in a new view, it uses a custom view for the preview. You can then manipulate the view's dimensions to fit your needs. Note that making it invisible using Visibility or by giving its parent size 0 will disable the camera. Giving it (for example) a height of 1 pixel will still work.
Example code (run with version 0.4.0 of the qr_code_scanner package)
import 'package:flutter/material.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'QR scanner demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String result = '';
QRViewController controller;
bool isVisible = true;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 200,
// 1 pixel, as a height 0 disables the view
height: isVisible ? 100 : 1,
child: QRView(
key: GlobalKey(),
onQRViewCreated: _onQRViewCreated,
),
),
Text(
'The barcode value was:',
),
Text(
'$result',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.visibility),
onPressed: () {
setState(() {
isVisible = !isVisible;
});
},
),
);
}
void _onQRViewCreated(QRViewController controller) {
this.controller = controller;
controller.scannedDataStream.listen((scanData) {
setState(() {
result = scanData.code;
});
});
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
}
I found that one solution is to put the QRView inside a stack, s.t. it is hidden under another view

Use different FABs with different tabs in Flutter, and change buttons while swiping between them?

I'm trying to set up tabs with FABs, like what's pictured in the Material Design guidelines.
I've pretty much got it working, by adding a listener on the TabController and changing my FAB there:
#override
void initState() {
...
_tabController = TabController(
length: 5,
vsync: this,
)..addListener(() {
setState(() {
_fabData = _fabDatas[_tabController.index];
});
});
...
}
#override
Widget build(BuildContext context) {
final fab = _fabData == null
? null
: FloatingActionButton(
isExtended: _fabData.expanded,
tooltip: _fabData.tooltip,
child: Icon(_fabData.icon),
onPressed: () {
_fabData.onPressed(context);
},
);
return Scaffold(
...
floatingActionButton: fab,
...
);
}
The problem is that tab controller listeners seem to be called only when the tab switch has finished completely, and not halfway through. If a user swipes from one tab to another, the tab will slide completely over, then come to a stop, and then the button will change.
Is there a way to trigger this in the middle of the swipe instead?
You do not need with listener, try this:
import 'package:flutter/material.dart';
class TabControllerApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Tabs work',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _fabData = [
'Tab1',
'Tab2',
'Tab3',
'Tab4',
'Tab5'
]; // Replace with your OBJECT!!!
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: _fabData.length,
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
bottom: TabBar(
tabs: _fabData
.map((String t) => Tab(
text: t,
))
.toList(),
),
),
body: TabBarView(
children: _fabData.map((String text) {
return Container(
child: Stack(
children: [
Positioned(
bottom: 16,
right: 16,
child: FloatingActionButton(
// TODO USE YOUR Object
isExtended: true, //_fabData.expanded,
//tooltip: _fabData.tooltip,
child: Icon(Icons.bookmark), //Icon(_fabData.icon),
onPressed: () {
//_fabData.onPressed(context);
},
),
)
],
),
);
}).toList()),
));
}
}

Flutter: How to perform two actions based on new state?

I am new to Flutter, and trying to understand how to to perform a series of actions based on a state of the widget.
I have a VERY basic app, based on the default new Flutter app with the clicks counter. I'd like that when the counter hits 10, the counter text highlights in red for 500ms (showing '10') and then the counter gets reset back to 0 and the text goes back to black (showing '0').
I was able to change the color to red when _counter==10, but unsure how to change it back to black and reset the counter after a set period of time. I'd also want to make the button "unclickable" during the 500ms.
It's simple just follow the code below,
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
bool isButtonEnabled = true;
Future<void> _incrementCounter() async {
_counter++;
if (_counter == 10) {
setState(() {
isButtonEnabled = false;
});
await Future.delayed(Duration(milliseconds: 500));
setState(() {
_counter = 0;
isButtonEnabled = true;
});
} else {
setState(() {});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style:
TextStyle(color: isButtonEnabled ? Colors.black : Colors.red),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: isButtonEnabled ? _incrementCounter : null,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
You're done.

show/hide a widget without recreating it

Let's say I have 2 cards and one is shown on screen at a time. I have a button that replaces the current card with other cards. Now assume that there is some data on card 1 and some data on card 2 and I don't want to destroy the data on each of them or I don't want to rebuild any of them again.
I tried using Stack Widget and overlapping one on top of others with a boolean on the top card. The value of this boolean is reversed by calling setstate when the button is pressed. The issue is as soon as I press the button, the new card rebuilds all over again and then shown or initState is called again, which I don't want. Any Solution?
EDIT: Sample Code:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var toggleFlag = false;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: toggleFlag
? CustomWidget(color: Colors.blue)
: CustomWidget(color: Colors.red),
),
floatingActionButton: new FloatingActionButton(
onPressed: _toggleCard,
tooltip: 'Increment',
child: new Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
void _toggleCard() {
setState(() {
toggleFlag = !toggleFlag;
});
}
}
class CustomWidget extends StatefulWidget {
var color;
CustomWidget({this.color});
#override
State<StatefulWidget> createState() {
return new MyState();
}
}
class MyState extends State<CustomWidget> {
#override //I don't want this to be called again and again
Widget build(BuildContext context) {
return new Container(
height: 100.0,
width: 100.0,
color: widget.color,
);
}
}
1-Solution:
You have an array of widgets like this
final widgetList[widget1(), widget2()]
int currentIndex = 0;
IndexedStack (
   index: currentIndex,
   children: widgetList,
 ));
2-Solution:
With the Stack widget
int currentIndex = 0;
Stack(
children: [
Offstage(
offstage: currentIndex != 0,
child: bodyList[0],
),
Offstage(
offstage: currentIndex != 1,
child: bodyList[1],
),
Offstage(
offstage: currentIndex != 2,
child: bodyList[2],
),
],
)
3-Solution:
You need to add this to your stateful widget state
AutomaticKeepAliveClientMixin <Widgetname> like this
class _WidgetState extends State <Widgetname> with AutomaticKeepAliveClientMixin <Widgetname> {
#override
   bool get wantKeepAlive => true;
}
just wrap that Widget inside a Visibility widget then set "maintainSate" to true
Visibility(
visible: toggleFlag,
maintainState: true,
child: const CustomWidget(),
)
Stateless widgets are always considered to be perishable. If you want to preserve state, use a StatefulWidget and a State subclass.