How do I programmatically simulate onTap on a button in Flutter? - flutter

For example:
// Update: This GestureDetector is embedded inside a third party package
// that will invoke a series of animation along with the onTap button
GestureDetector(
onTap: () => print('Hey There!'),
child: Widget1(),
)
// Then another place in the same screen
GestureDetector(
onDoubleTap: () {
//Call the onTap of Widget1's GestureDetector
print('I'm Here');
}
child: Widget2(),
)
What I wanted is when a user double tap Widget2, it will also invoke the onTap call back of Widget1.
Update:
So I do not want to just invoke a function passed into the onTap of GestureDetector of Widget1, but rather to programmatically tap the onTap of Widget1's GestureDetector
How do I do that?

You can do something like this -
Create your gesture detector -
GestureDetector gestureDetector = GestureDetector(
onTap: () {
setState(() {
_lights = !_lights;
});
},
child: Container(
color: Colors.yellow.shade600,
padding: const EdgeInsets.all(8),
child: const Text('TURN LIGHTS ON'),
),
);
Create a button (or any widgetthat you would like to use) to call onTap on GestureDetector gestureDetector.onTap() just like you call method on another widget. (I am using a FlatButton here)-
FlatButton(
color: Colors.blue,
textColor: Colors.white,
disabledColor: Colors.grey,
disabledTextColor: Colors.black,
padding: EdgeInsets.all(8.0),
onPressed: () {
//Trigger the GestureDetector onTap event.
gestureDetector.onTap();
},
child: Text("Click Here"),
),
Now you can click on the FlatButton to call the onTap event on GestureDetector.
Here is the complete example -
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Gesture Detector On Tap'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _lights = false;
#override
Widget build(BuildContext context) {
GestureDetector gestureDetector = GestureDetector(
onTap: () {
setState(() {
_lights = !_lights;
});
},
child: Container(
color: Colors.yellow.shade600,
padding: const EdgeInsets.all(8),
child: const Text('TURN LIGHTS ON'),
),
);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Container(
alignment: FractionalOffset.center,
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(
Icons.lightbulb_outline,
color: _lights ? Colors.yellow.shade600 : Colors.black,
size: 60,
),
),
gestureDetector,
SizedBox(height: 50.0),
FlatButton(
color: Colors.blue,
textColor: Colors.white,
disabledColor: Colors.grey,
disabledTextColor: Colors.black,
padding: EdgeInsets.all(8.0),
onPressed: () {
gestureDetector.onTap();
},
child: Text("Click Here"),
),
],
),
),
),
);
}
}
You will get something like this -

Update: So I do not want to just invoke a function passed into the onTap of GestureDetector of Widget1, but rather to programmatically tap the onTap of Widget1's GestureDetector
The purpose of onTap is to call the callback function inside the onTap. So I'm not sure why you just want to tap the button other than invoking functions that should be called when tapping that button (Can you elaborate on this?).
If you want to simulate the tap for testing, you can do that with Flutter Driver using driver.tap()

After several false starts, this is what worked for me. I use Riverpod, and formFocusIdProvider in this example code is a simple StateProvider.
I'm actually not clear why I needed to add the delay - but without that the behavior was unpredictable with the widget repaint.
This code is in the build method.
ref.listen(formFocusIdProvider, (previous, next) {
if (<some condition>) {
Future.delayed(const Duration(milliseconds: 200), () {
if (mounted) {
onTapFunction();
}
});
}
});

just make the first one's function separately
void firstFunction(){
print('hey There!');
}
like this, then call it in the second widget
so your code will look like this:
GestureDetector(
onTap: () => firstFunction(),
child: Widget1(),
)
// Then another place in the same screen
GestureDetector(
onDoubleTap: () {
firstFunction();
print('I'm Here');
}
child: Widget2(),
)

Related

Open Drawer of current (topmost) route's Scaffold

I'm writing an audio player. Like most media players (Youtube, Spotify, etc), I want a "remote" overlay on the screen while media is playing. No matter what the user is doing, they should be able to control the media.
I accomplished that with a Stack under MaterialApp
MaterialApp(
title: 'MyApp',
navigatorObservers: [gRouteObserver],
routes: appRoutes,
builder: (context, child) {
return Stack(children: [
child!,
Positioned(
bottom: 55,
width: MediaQuery.of(context).size.width,
child: StatefulBuilder(builder: (context, _setState) {
gPlayer.widgetSBRefresher = _setState;
return gPlayer.started ? gPlayer.widget : const SizedBox(height: 0);
}))
]);
});
gPlayer.widget references this
class MiniPlayer extends StatefulWidget {
const MiniPlayer({Key? key}) : super(key: key);
#override
State<MiniPlayer> createState() => MiniPlayerState();
}
class MiniPlayerState extends State<MiniPlayer> with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(context) {
super.build(context);
return Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
color: Colors.black,
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Column(
children: [
Material(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AvatarAlone(id: gPlayer.current!.owner),
Expanded(
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Text(gPlayer.playing
? "Now Playing"
: "Paused"),
),
),
// here is the code I'll
// be talking about -->
IconButton(
color: Colors.white,
iconSize: 20,
icon: const Icon(MyIcons.bookmark),
onPressed: gPlayer.bookmarkBuilder,
),
InkWell(child: Icon(gPlayer.playing ? MyIcons.pauseCircle : MyIcons.playCircle, size: 50), onTap: gPlayer.playPause)
],
),
),
],
),
),
),
));
}
refresh() {
setState(() {});
}
}
I used a code comment to point out this icon button.
IconButton(
color: Colors.white,
iconSize: 20,
icon: const Icon(MyIcons.bookmark),
onPressed: gPlayer.bookmarkBuilder,
),
So, when this widget is open and the app is on the home route ("/"), I can do
bookmarkBuilder() {
Scaffold.of(gScaffApp.currentContext!).openDrawer();
}
and it will open the drawer.
I've attached the same drawers to all my routes' scaffolds.
When other routes are up, with their own scaffolds, I want bookmarkBuilder to open the drawer on the topmost route. But I can't quite figure out how.
So I have a working solution to this, but I don't love it.
I created a global variable, gScaffs, with gScaffApp as the first element.
List<GlobalKey<ScaffoldState>> gScaffs = [gScaffApp];
My secondary routes all use the same base scaffold widget
class _CardScaffoldState extends State<CardScaffold> {
#override
initState() {
super.initState();
gScaffs.add(GlobalKey());
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(key: gScaffs.last,
drawer: DrawerBookmarks()
...
And the dispose method looks like this.
#override
dispose() {
super.dispose();
gScaffs.removeLast();
}
And then, in my bookmarkBuilder function, I have this.
It's not clear to me why, but gScaffApp needs the drawer triggered one way, while the CardScaffolds need the drawer triggered the other way.
bookmarkBuilder() {
if (gScaffApp == gScaffs.last) {
Scaffold.of(gScaffApp.currentContext!).openDrawer();
} else {
gScaffs.last.currentState!.openDrawer();
}
}

How to Change Swiping direction of CupertinoPageRoute

I’m using Cupertino Page Route to produce the following Behavior
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
//doesn't do what I want to do
GestureDetector(
onTap: () => Navigator.of(context).pushNamed(settingsPage),
onPanUpdate: (details){
if(details.delta.dx < 0){
Navigator.of(context).pushNamed(settingsPage);
}
},
child: Icon(Icons.settings),
),
//Not my concern for now
IconButton(
onPressed: () {},
icon: const Icon(Icons.notifications, color: Colors.black),
),
//Does what I want it to do
GestureDetector(
onTap: () => Navigator.of(context).pushNamed(messages),
child: Icon(Icons.message),
),
],
),
It allows me to swipe from Left to Right to close the Message Screen
Unfortunately it does not allow me to close the Settings Screen by swiping from Right to Left
Anyone one knows how to change the direction of the swipe for CupertinoPageRoute or mimic it's behavior with an other Solution ?
I don’t think you can easily change the swiping direction of CupertinoRoute but I’ve found two easy solutions for you :
You could either use a PageView Widget - which allows you to swipe between screens
Or - better solution for your specific situation :
You could instead use the page_transition Package with a Swipe Direction Detector
This Second Solution will best reproduce the behavior of CupertinoPageRoute
in addition it will provide you with animations going from HomeScreen to Settings/Messages but also back to your HomeScreen :
This is how you could implement this second solution shown in the GIF above into your App :
import 'package:page_transition/page_transition.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
_HomeScreen createState() => _HomeScreen();
}
class _HomeScreen extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
automaticallyImplyLeading: false,
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () => Navigator.push(
context,
//Use Page Transition left to right here
PageTransition(
// duration: Duration(seconds: 1),
type: PageTransitionType.leftToRightWithFade,
child: SettingsScreen(),
inheritTheme: true,
ctx: context),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: const Icon(Icons.settings, color: Colors.black),
)),
IconButton(
onPressed: () {},
icon: const Icon(Icons.notifications, color: Colors.black),
),
GestureDetector(
onTap: () => Navigator.push(
context,
//Use Page Transition right to left here
PageTransition(
// duration: Duration(seconds: 1),
type: PageTransitionType.rightToLeftWithFade,
child: const MessageScreen(),
inheritTheme: true,
ctx: context),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: const Icon(Icons.message, color: Colors.black),
)),
],
),
),
//body
body: Container(),
);
}
}
Then in your Settings/Messages Screens just implement a GestureDetector which will trigger on Swipe
It will Navigator.pop (close) your screen - & Flutter will take care of the reverse animation
class SettingsScreen extends StatefulWidget {
const SettingsScreen({Key? key}) : super(key: key);
#override
_SettingsScreen createState() => _SettingsScreen();
}
class _SettingsScreen extends State<SettingsScreen> {
#override
Widget build(BuildContext context) {
return GestureDetector(
// This is where you detect swiping from right to left
onPanUpdate: (details) {
if (details.delta.dx < -10) {
// Upon swiping detection it then pops the screen - reverse animation happens without additional code
Navigator.pop(context);
}
},
child: const Scaffold(
backgroundColor: Colors.blueGrey,
body: Center(child: const Text("🥸 Settings")),
));
}
}
// Same code here for your MessageScreen but with opposite swipe gesture detection (left to right)
class MessageScreen extends StatefulWidget {
const MessageScreen({Key? key}) : super(key: key);
#override
_MessageScreen createState() => _MessageScreen();
}
class _MessageScreen extends State<MessageScreen> {
#override
Widget build(BuildContext context) {
return GestureDetector(
onPanUpdate: (details) {
if (details.delta.dx > 10) {
Navigator.pop(context);
}
},
child: const Scaffold(
backgroundColor: Colors.redAccent,
body: Center(child: const Text("😎 Messages")),
));
}
}
That's it ! - the only down side to this method is once the swipe has been detected there is no way to cancel it
---- upon long discussion with the user - comments bellow have been addressed above & question edited

Flutter Floating action button error! Trying to create a row of button with a responsive touch effect

My Code:
bool _isClicked = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 3.0),
child: Container(
decoration: BoxDecoration(
color: _isClicked ? Colors.orange[300] : Colors.white,
borderRadius: BorderRadius.circular(30.0),
),
child: FlatButton(
splashColor: Colors.orange[300],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
),
onPressed: () {
setState(() {
_isClicked = !_isClicked;
});
},
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 20.0,
),
child: Text(
foodItem,
style: TextStyle(
fontSize: 20.0,
color: _isClicked ? Colors.white : Colors.grey[700],
),
),
),
),
),
),
);
Reality:
Expectation:
When I click one button, only that turns orange the rest stay white.
When I click it back again, it turns grey again just like the rest.
I believe you want to achieve some kind toggle behavior for the buttons. Though ToggleBar widget is good for this it is not flexible with it expectations about child widgets. So a ButtonBar widget would be helpful with some kind internal state about the buttons which are clicked. Here is a working solution which might help you. The same code is available as a codepen here.
Approach
Extracted your code for the button into a widget called TButton with parameters as follows
isClicked - a boolean flag to denote if the button is clicked.
foodItem - the text to be displayed on the button.
onPressed - a callback function to be called when the button is pressed.
In the parent widget MyButtons hold a list of bool indicating the status of click for each button.
MyButtons accepts a list of foodItems. Iterate this list and generate a list of TButton widget and pass it to the ButtonBar as children.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue, scaffoldBackgroundColor: darkBlue),
home: Scaffold(
body: MyButtons(foodItems: ['Pizza', 'Burger', 'Kebab']),
),
);
}
}
class MyButtons extends StatefulWidget {
MyButtons({Key key, this.foodItems}) : super(key: key);
final List<String> foodItems;
#override
_MyButtonsState createState() => _MyButtonsState();
}
class _MyButtonsState extends State<MyButtons> {
List<bool> isSelected;
#override
initState() {
super.initState();
// initialize the selected buttons
isSelected = List<bool>.generate(widget.foodItems.length, (index) => false);
}
#override
Widget build(BuildContext context) {
return Padding(
// just for aesthetics
padding: const EdgeInsets.only(top: 80.0),
child: ButtonBar(
// use the alignment to positon the buttons in the screen horizontally
alignment: MainAxisAlignment.center,
// iterate over the foodItems and generate the buttons.
children: widget.foodItems.asMap().entries.map((entry) {
return TButton(
isClicked: isSelected[entry.key],
foodItem: entry.value,
onPressed: () {
setState(() {
isSelected[entry.key] = !isSelected[entry.key];
});
});
}).toList(),
),
);
}
}
class TButton extends StatelessWidget {
final bool isClicked;
final String foodItem;
/// OnPressed is passed from the parent. This can be changed to handle it using any state management.
final Function onPressed;
TButton(
{#required this.isClicked,
#required this.foodItem,
#required this.onPressed});
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: isClicked ? Colors.orange[300] : Colors.white,
borderRadius: BorderRadius.circular(30.0),
),
child: FlatButton(
splashColor: Colors.orange[300],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
),
onPressed: onPressed,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 20.0,
),
child: Text(
foodItem,
style: TextStyle(
fontSize: 20.0,
color: isClicked ? Colors.white : Colors.grey[700],
),
),
),
),
);
}
}

Button OnPressed is not working in Flutter

I have issue with the Raised button click. The method inside OnPressed() is not getting called. Ideally on the OnPressed() method i would like to have the pop up or a slider shown. I have created the example to show the problem currently faced.
The main.dart file calls Screen2()
import 'package:flutter/material.dart';
//import 'package:flutter_app/main1.dart';
import 'screen2.dart';
void main() => runApp(Lesson1());
class Lesson1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Screen2(),
);
}
}
and in Screen2()i have just have a RaisedButton and OnPressed() it needs to call the function ButtonPressed().
import 'package:flutter/material.dart';
class Screen2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,
title: Text('Screen 2'),
),
body: Center(
child: RaisedButton(
color: Colors.blue,
child: Text('Go Back To Screen 1'),
onPressed: () {
print('centrebutton');
ButtonPressed();
},
),
),
);
}
}
class ButtonPressed extends StatefulWidget {
#override
_ButtonPressedState createState() => _ButtonPressedState();
}
class _ButtonPressedState extends State<ButtonPressed> {
#override
Widget build(BuildContext context) {
print ('inside button press');
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.red,
title: Text('Screen 3'),
),
// create a popup to show some msg.
);
}
}
On clicking the Raised button the print statement ('centerbutton') gets printed.
But the ButtonPressed() method is not getting called .
I am not able to see the print msg('inside button press') in the console. Pl. let me what could be the reason for ButtonPressed method not getting called. Attached the snapshot for your reference.
You are calling a Widget on your RaisedButton's onPressed method. Your widget is getting called but will not render anywhere in the screen.
You should call a function for processing your data in a tap event. But you are calling a widget or say a UI view.
If you want to navigate to the respective screen then you should use navigator.
For ex :
onPressed: () {
print('centrebutton');
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => ButtonPressed()));
},
This could be a cause: If you have an asset as a child of your button, and that asset does not exist, the button onpressed will not work.
Solution: remove the asset.
Example:
return RaisedButton(
splashColor: Colors.grey,
onPressed: () {
},
child: Padding(
padding: const EdgeInsets.fromLTRB(0, 10, 0, 10),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image(image: AssetImage("assets/google_logo.png"), height: 35.0), //remove this line
Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
'Sign in with Google',
style: TextStyle(
fontSize: 20,
color: Colors.grey,
),
),
)
],
),
),
);

onPressed call not defined

Working on an App that will require multiple screens. The below right now shows only two icons, more later, and i need them the be able to go the a corresponding screen when pressed. Everything works but the onPressed function. The error I get is
The named parameter "onPressed" is not defined
Do I have the onPressed function in the wrong spot? I have tried moving it between other functions but I get the same error.
Any help is appreciated
main.dart
import 'package:flutter/material.dart';
import './food_screen.dart';
void main(List<String> args) {
runApp(new MaterialApp(
home : MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title :Text('Main Title'),
backgroundColor: Colors.blue,
),
backgroundColor: Colors.blue[100],
body: Container(
padding: EdgeInsets.all(30.0),
child: GridView.count(
crossAxisCount: 2,
children: <Widget>[
Card(
margin: EdgeInsets.all(8.0),
child: InkWell(
onTap: (){
Navigator.push(context,
MaterialPageRoute(builder: (context)=>FoodScreen())
);
},
splashColor: Colors.blue,
child: Center(
child: Column(
children: <Widget>[
Icon(Icons.fastfood, size: 70.0),
Text("FOOD", style: new TextStyle(fontSize: 28.0))
]
)
),
),
),
Card(
margin: EdgeInsets.all(8.0),
child: InkWell(
onTap: (){},
splashColor: Colors.blue,
child: Center(
child: Column(
children: <Widget>[
Icon(Icons.directions_car, size: 70.0),
Text("VEHILCES", style: new TextStyle(fontSize: 28.0))
],
),
),
),
),
]
)
)
);
}
}
food_screen.dart
import 'package:flutter/material.dart';
import './main.dart';
class FoodScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
);
}
}
Card doesn't support onPressed property, you already have InkWell which has onTap, you can put onPressed method action inside it.
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context)=>FoodScreen())
);
}
Card doesn't have any property of onpressed()
you can add a floating button and Route it to the the second page i.e food_screen.dart
https://api.flutter.dev/flutter/material/Card-class.html
if you want to add a tap on Card Widget just wrap the card with GestureDetector.