I'm trying to use Flutter FAB in PageView, but since the FAB takes a variable that is initialized in the page 2, it creates an error.
So how can i show the FAB only after the second page?
You can use PageView on top level, and use scaffold on every item.It can also be done by adding listener to the pageController.
class FabG extends StatefulWidget {
const FabG({super.key});
#override
State<FabG> createState() => _FabGState();
}
class _FabGState extends State<FabG> {
bool isFabActivePage = false;
late final PageController controller = PageController()
..addListener(() {
if (controller.hasClients && (controller.page ?? 0) < 2) { //your logic here
isFabActivePage = false;
setState(() {});
} else if (isFabActivePage == false) {
isFabActivePage = true;
setState(() {});
}
});
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton:
isFabActivePage ? FloatingActionButton(onPressed: () {}) : null,
body: PageView.builder(
controller: controller,
itemBuilder: (context, index) => Center(child: Text("page ${index+1}")),
),
);
}
}
The solution I used was to set a callback with the bool floating.
on the home page:
`
bool floating = false;
showFAB(show) {
setState(() {
floating = show;
});
}`
floatingActionButton:(floating) ? FloatingMenu(pageController: pageController) : null,
then on the initState function of second page used the callback to change the bool value
Related
I am building a chess clock in flutter. when i change clocks time duration in settings page i want to ask the user for confirmation using alert dialog. I want the alert dialog to only appear in homepage right now it appears immediately after I change duration in settings page.
I am using provider to manage state when I change duration I change a bool value to true. then in the home page I check if the bool value is true then run the alert dialog code. I tried putting dialog code in future delay.Zero but it did not work.
right now home page rebuilds immediatly after i change settings and alert dialog pops up.
my code:
// home page code
class HomePage extends StatefulWidget {
const HomePage({
Key? key,
}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with TickerProviderStateMixin, WidgetsBindingObserver {
// code for initstate and logic
late AnimationController _whiteControl;
late AnimationController _blackControl;
int whiteTime = 1;
int blackTime = 1;
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
_whiteControl = AnimationController(
vsync: this,
duration: Duration(seconds: whiteTime * 60),
);
_blackControl = AnimationController(
vsync: this,
duration: Duration(seconds: blackTime * 60),
);
#override
void dispose() {
WidgetsBinding.instance!.removeObserver(this);
_whiteControl.dispose();
_blackControl.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
var gameState = Provider.of<GameState>(context, listen: false);
final ChoiceDialog resetChoice = ChoiceDialog(
buttonOkOnPressed: () {
_blackControl.reset();
_whiteControl.reset();
gameState.isgameChanged = false;
Navigator.pop(context);
},
buttonCancelOnPressed: () {
gameState.isgameChanged = false;
Navigator.pop(context);
},
);
if (gameState.isGameStarted && gameState.isgameChanged) {
Future.delayed(Duration.zero, () {
print('👉 Game Changed ');
resetChoice.show(context);
});
}
print('🏠 Rebuilding Home Page ---');
return Scaffold();
//reset of homepage code
Settings Page Code:
class SettingsPage extends StatelessWidget {
const SettingsPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Settings'),
),
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: gamesList.length,
itemBuilder: (context, index) {
return Consumer<GameState>(
builder: (context, _gameState, child) {
return GestureDetector(
onTap: () {
_gameState.changeGame(gamesList[index]);
},
child: Container(),
);
},
);
}),
)
],
),
);
}
}
Provider State Logic:
class Game {
final String name;
final int whiteTime;
final int blackTime;
Game({
required this.name,
required this.whiteTime,
required this.blackTime,
});
}
class GameState with ChangeNotifier {
bool turnWhite = false;
bool turnBlack = false;
bool isgameChanged = false;
bool isClocksFinished = false;
bool isPaused = false;
bool isGameStarted = false;
bool resetGame = false;
int white = 0;
int black = 0;
Game _currentGame = gamesList[0];
Game get currentGame => _currentGame;
bool get isWhiteTurn => turnWhite;
void resetGameClocks() {
resetGame = true;
notifyListeners();
}
void setTurnWhite() {
turnWhite = true;
turnBlack = false;
isGameStarted = true;
notifyListeners();
}
void setTurnBlack() {
turnWhite = false;
turnBlack = true;
isGameStarted = true;
notifyListeners();
}
void changeGame(Game game) {
_currentGame = game;
isgameChanged = true;
white = game.whiteTime;
black = game.blackTime;
notifyListeners();
}
}
project repository: https://github.dev/tonydavidx/chess-clock-app/tree/dev
I have checked your code you did a blunder in a very initial point/starting point of the Homepage screen kindly avoid this
never ever initialize or put your code before returning in build function of widgets if you really need some logic or code to be executed in the beginning then put it inside an initstate() or inside your provider constructor and handle it properly.
this is the root cause of the problem which you are facing.
i am newbie to flutter and i created a tab bar and tab bar view by adding dynamic content as following code. when click on tab in tab bar it works fine. but swiping tab bar view new value always detect on another swipe for example move to tab one tab bar view says 0. move to tab 2 tab bar view says 1.
please help me friends.
#override
void initState() {
super.initState();
setState(() {
_isLoading = true;
num = 0;
for(int i=0;i<3;i++){
_tabs.add(Tab(text: '${i + 1}'));
tabView.add(getWidget());
}
_tabController = TabController(vsync: this, length: _tabs.length, initialIndex: 0);
_tabController.addListener((){
print('index${_tabController.index}');
setState(() {
num = _tabController.index ;
});
});
Widget getWidget(){
return Builder(builder: (BuildContext context) {
return Text('${num}');
});
}
#override
Widget build(BuildContext context) {
return
DefaultTabController(
length: _tabs.length,
child:Scaffold(
appBar: AppBar(
title: Text("cart"),
bottom: TabBar(
controller: _tabController,
tabs: _tabs,
),
),
body: TabBarView(
controller: _tabController,
children:tabView,
),
});
Use AutomaticKeepAliveClientMixin
override wantKeepAlive property and return true
Instead of this:
tabView.add(getWidget());
use:
tabView.add(Tabpageview(TabData(i)));
class Tabpageview extends StatefulWidget {
final tabData;
Tabpageview(this.tabData);
#override
_TabpageviewState createState() => _TabpageviewState();
}
class _TabpageviewState extends State<Tabpageview>
with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Tab(child: Center(child: Text('Tab : ${widget.tabData.data}')));
}
}
class TabData {
int data;
TabData(this.data);
}
Modify your code with some smooth animation, plz modify setState() method as per your need.
final aniValue = _tabController.animation.value;
if (aniValue > 0.5 && index != 1) {
products.forEach((item) {
setState(() {
displayProducts.add(item);
});
});
} else if (aniValue <= 0.5 && index != 0) {
products.forEach((item) {
setState(() {
displayProducts.add(item);
});
});
}
I am using a PageView.builder to manage a set of 5 pages. The PageView widget allows the user to swipe left/right to navigate between the pages. I can also use a PageController to programatically navigate to different pages, for example at the press of a button. This is all desired behavior. With that said, on the first page I have a form that I would like to validate before allowing the user to continue. With the button, I can just call the form's validation method to check before navigating away, like this:
onPressed: () {
if (_formKey.currentState.validate()) {
//Navigate to next page...
_pageController.nextPage(duration: Duration(milliseconds: 300), curve: Curves.linear);
}
},
My question is: how can I perform this validation before allowing the user to navigate using the swiping gesture?
Thanks
You can modify the physics of the PageView:
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
StreamController isValidController = StreamController();
bool isValid = false;
List pages;
#override
void initState() {
super.initState();
isValidController.stream.listen((value){
setState(() {
isValid = value;
});
});
pages = [
FormPage(isValidController: isValidController, formKey: _formKey,),
SecondPage(),
ThirdPage(),
FourthPage(),
FifthPage(),
];
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView.builder(
physics: isValid ? ClampingScrollPhysics() : NeverScrollableScrollPhysics(), //changing here
itemBuilder: (_, index) {
return pages[index];
},
),
);
}
#override
void dispose() {
isValidController.close();
super.dispose();
}
And in the Page with the Form:
class FormPage extends StatelessWidget {
final GlobalKey<FormState> formKey;
final StreamController isValidController;
const FormPage({Key key, this.formKey, this.isValidController});
#override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Align(
child: RaisedButton(
onPressed: () {
if (formKey.currentState.validate()) {
isValidController.add(true);
}
},
),
),
);
}
}
I have a parent widget which calls a custom Switch widget that I've made. I need the value of the switch(whether it's ON or OFF) in my parent widget. Can I create a controller in my switch widget which would return that value?
Currently, I am passing the function from my parent widget which changes the value of a boolean which is in my parent widget based on the value of the switch in the switch widget.
Parent widget:
bool isSwitchOn = false;
Switch(onSwitchToggle: (bool val) {
isSwitchOn = val;
})
Custom Switch widget:
class Switch extends StatefulWidget {
Widget build(BuildContext context) {
return CupertinoSwitch(
value: widget.value,
onChanged: (bool value) {
setState(() {
widget.value = value;
});
widget.onSwitchToggle(value);
},
),
}
The switch widget is used everywhere in the code whenever I need a switch and sometimes I don't need to know the state of the switch and I just need to execute a function when the switch is toggled but the way I've written the code, I'll need to pass bool everywhere whenever I call the switch. Looking for a better way to do it.
eg: Bool val is unnecessary because i won't need it.
Switch(onSwitchToggle: (bool val) {
print('abc')
})
You can easily solve it by using a ChangeNotifier. That's actually also the way it's solved in TextFieldController and ScrollController.
I have some sample code for you based on what you described:
class SwitchController extends ChangeNotifier {
bool isSwitchOn = false;
void setValue(bool value) {
isSwitchOn = value;
notifyListeners();
}
}
Now your widget that wraps the Switch:
class CustomSwitch extends StatefulWidget {
CustomSwitch({
required this.controller
});
final SwitchController controller;
#override
State<StatefulWidget> createState() {
return _CustomSwitchState();
}
}
class _CustomSwitchState extends State<CustomSwitch> {
#override
Widget build(BuildContext context) {
return CupertinoSwitch(
onChanged: (bool value) {
widget.controller.setValue(value);
},
value: widget.controller.isSwitchOn,
);
}
}
Just listen to the change event of the native switch and set the value of the controller. Then notify the observers about it.
Then you can create the widget and pass a controller you add a listener to:
class SwitchTest extends StatefulWidget {
#override
_SwitchTestState createState() => _SwitchTestState();
}
class _SwitchTestState extends State<SwitchTest> {
SwitchController controller = SwitchController();
#override
void initState() {
controller.addListener(() {
setState(() {
print(controller.isSwitchOn);
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
CustomSwitch(
controller: controller
),
Text(controller.isSwitchOn.toString()),
],
),
),
);
}
}
I have written a detailed tutorial on how to create such custom controllers on my blog: https://www.flutterclutter.dev/flutter/tutorials/create-a-controller-for-a-custom-widget/2021/2149/
I need the run a method in a setState() after some previous variables in the same setState() have been updated. At the moment it waits for the method to complete before updating the page.
(I'm new to Flutter and my programming skills aren't very good).
I have tried splitting the setState() into different ones:
// this method is called once the animation has finished
void _solve() {
setState(() {
_isSolving = true; // I need to update the display here
});
setState(() {
_solution = PuzzleSolver().solve(); // this takes some time and returns a map
});
setState(() {
_isSolving = false; // and I need to update the display again here
});
}
But this didn't help, because I don't really know how it all works.
Here's the stripped down version of the code:
import 'package:flutter/material.dart';
import './puzzleSolver.dart';
class SomePage extends StatefulWidget {
#override
_SomePageState createState() => _SomePageState();
}
class _SomePageState extends State<SomePage> with TickerProviderStateMixin {
AnimationController animationController;
Map _solution = {};
bool _isSolving = false;
void initState() {
super.initState();
animationController = AnimationController(
vsync: this, duration: Duration(seconds: 5))
..addStatusListener(
(state) => (state == AnimationStatus.dismissed) ? _solve() : null); // run _solve() once the animation has finished
animationController.reverse();
}
// this method is called once the animation has finished
void _solve() {
setState(() {
_isSolving = true; // I need to update the display here
_solution = PuzzleSolver().solve(); // this takes some time and returns a map
_isSolving = false; // and I need to update the display here again
});
// at the moment it updates the display here
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Some Page'),
),
body: Column(
children: <Widget>[
(_isSolving == false && _solution.isEmpty)
? Text('hello world') // only show when it's not solving and there is no solution
: RaisedButton(
child: Text('show solution'),
onPressed: (_isSolving)
? null // disable the button when its solving
: () {}, // enable it when its solved
),
AnimatedBuilder(
animation: animationController,
builder: (BuildContext context, Widget child) {
return Container(); // this is where the animated widgets would be
}
),
],
),
);
}
}
Assuming the PuzzleSolver().solve() method is a Future, you can do the following:
void _solve() async{
setState(() {
_isSolving = true;
});
_solution = await PuzzleSolver().solve(); // waits for the future to finish
setState(() {
_isSolving = false;
});