class SnackBarPage extends StatelessWidget {
const SnackBarPage({super.key});
#override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
onPressed: () {
final snackBar = SnackBar(
content: const Text('Yay! A SnackBar!'),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
// Some code to undo the change.
},
),
);
// Find the ScaffoldMessenger in the widget tree
// and use it to show a SnackBar.
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
child: const Text('Show SnackBar'),
),
);
}
}
Above is the code that I want to translate into ClojureDart.
However, ClojureDart is a dialect of Clojure which is a functional language,
Found!
I had to use cljd.flutter.alpha library that exports f/widget, and wraps it around the scaffold.
Then, within the widget, I had to use the inherit keyword with the widget I wanted to inherit, in my case, ScaffoldMessenger. After doing so, I have access to the ScaffoldMessenger object with a variable with a kebab-case name. While it can be confusing without seeing it, it gives something like that :
(f/widget
:inherit [m/ScaffoldMessenger]
(m/Scaffold
...
:onPressed (.showSnackBar scaffold-messenger snackbar)
...))
With scaffold-messenger variable corresponding to m/ScaffoldMessenger, and snackbar being a function that returns a m/SnackBar widget.
Related
How can I simulate tap event on a Flutter widget?
For example, how do I simulate tapping on a Tabbar title? And more importantly, how can I find the widget in the first place? Please note that I don't want to call the relevant callbacks, nor testing widgets using Flutter test classes, but I'd like to simulate the tapping on widgets at runtime instead.
EDIT 1: (Please Note)
I DO NOT want to test a widget or call a method that is assigned to a GestureDetector.onTap or RaisedButton.onPressed etc...
First, obtain a RenderBox. Then just call hitTest method. Any will do, as long as it's mounted in the tree.
To do so you'll have to use BuildContext through context.findRenderObject().
BuildContext context;
final renderObj = context.findRenderObject();
if (renderObj is RenderBox) {
final hitTestResult = HitTestResult();
if (renderObj.hitTest(hitTestResult, position: /* The offset where you want to "tap" */)) {
// a descendant of `renderObj` got tapped
print(hitTestResult.path);
}
}
Use GestureBinding to simulate a click event anywhere on the screen:
Full example:
import 'package:flutter/gestures.dart';
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 MaterialApp(
home: Scaffold(
body: Column(
children: [
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
primary: Colors.red[900],
),
child: SizedBox(
width: 400,
height: 400,
child: Center(child: Text("I'm a big button.")),
),
),
ElevatedButton(
onPressed: () async {
GestureBinding.instance!.handlePointerEvent(PointerDownEvent(
position: Offset(200, 300),
));
await Future.delayed(Duration(milliseconds: 500));
GestureBinding.instance!.handlePointerEvent(PointerUpEvent(
position: Offset(200, 300),
));
},
child: Text('Simulate Click'),
),
],
),
),
);
}
}
I have function inside my Inherited widget, I would like to use this function with parameters, but I don't get how that can be done. for instance the function below works fine to have inside the inherited widget:
final Function onTap;
However I would like to use something like below:
final Function onTap(String name);
Anyone know if this can be done somehow and in that case how? any help or input appreciated, thanks!
You can use
onTap: () => onTap('foo')
instead of
onTap: onTap
if you want to pass additional parameters
Not sure if that's what you want to do:
You can use typedef to define a callback interface.
import 'package:flutter/material.dart';
typedef void NameTapCallback(String name);
class FooWidget extends StatelessWidget {
final NameTapCallback onTap;
const FooWidget({Key key, this.onTap}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
RaisedButton(
onPressed: () {
onTap('Peter');
},
child: Text('Peter'),
),
RaisedButton(
onPressed: () {
onTap('Paul');
},
child: Text('Paul'),
)
],
);
}
}
// Usage:
var myFooWidget = FooWidget(
onTap: (name) {
print('$name tapped');
},
);
I doing a AlertDialog, so when I tried to insert Slider widget inside the state of value sound realy stranger, and this doesn't happens if Slider is outside of AlertDialog
new Slider(
onChanged: (double value) {
setState(() {
sliderValue = value;
});
},
label: 'Oi',
divisions: 10,
min: 0.0,
max: 10.0,
value: sliderValue,
)
The complete widget code of AlertDialog
Future<Null> _showDialog() async {
await showDialog<Null>(
context: context,
builder: (BuildContext context) {
return new AlertDialog(
title: const Text('Criar novo cartão'),
actions: <Widget>[
new FlatButton(onPressed: () {
Navigator.of(context).pop(null);
}, child: new Text('Hello'))
],
content: new Container(
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new Text('Deseja iniciar um novo cartão com quantos pedidos ja marcados?'),
new Slider(
onChanged: (double value) {
setState(() {
sliderValue = value;
});
},
label: 'Oi',
divisions: 10,
min: 0.0,
max: 10.0,
value: sliderValue,
)
],
),
),
);
}
);
}
and everything is under State class of StatefullWidget.
Its look like doesn't update the value and when try to change the value keep in same position.
Update 1
The problem is there are 2 required parameters in Slider (onChanged, value), So I shoud update this or UI keep quite, see the video how the aplication is running
Video on Youtube
Update 2
I've also opened a issue to get help with this at Github repository, if someone wants to get more information can go to issue #19323
The problem is that it's not your dialog that holds the state. It's the widget that called showDialog. Same goes for when you call setState, you are calling in on the dialog creator.
The problem is, dialogs are not built inside build method. They are on a different widget tree. So when the dialog creator updates, the dialog won't.
Instead, you should make your dialog stateful. Hold the data inside that dialog. And then use Navigator.pop(context, sliderValue) to send the slider value back to the dialog creator.
The equivalent in your dialog would be
FlatButton(
onPressed: () => Navigator.of(context).pop(sliderValue),
child: Text("Hello"),
)
Which you can then catch inside the showDialog result :
final sliderValue = await showDialog<double>(
context: context,
builder: (context) => MyDialog(),
)
I've come up with the same issue with a checkbox and that's my solution, even if it's not the best approach. (see the comment in the code)
Future<Null>_showDialog() async {
return showDialog < Null > (
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return new AlertDialog(
title: Text("title"),
content: Container(
height: 150.0,
child: Checkbox(
value: globalSearch,
onChanged: (bool b) {
print(b);
globalSearch = b;
Navigator.of(context).pop(); // here I pop to avoid multiple Dialogs
_showDialog(); //here i call the same function
},
)),
);
},
);
}
Easiest and least amount of lines:
Use StatefulBuilder as top widget of Content in the AlertDialog.
StatefulBuilder(
builder: (context, state) => CupertinoSlider(
value: brightness,
onChanged: (val) {
state(() {
brightness = val;
});
},
),
));
I had similar issue and resolved by putting everything under AlertDialog in to a StatefullWidget.
class <your dialog widget> extends StatefulWidget {
#override
_FilterDialogState createState() => _FilterDialogState();
}
class _<your dialog widget> extends State<FilterDialog> {
#override
Widget build(BuildContext context) {
return AlertDialog(
//your alert dialog content here
);
}
}
create a statefull class with the slider at the return time and the double value should declare inside the statefull class thus the setstate func will work.
here is an example i done this for my slider popup its same for alert dialog use can declare the variable as global thus it can be accessed by other classes
class _PopupMenuState extends State<PopupMenu> {
double _fontSize=15.0;
#override
Widget build(BuildContext context) {
return Container(
child: Slider(
value: _fontSize,
min: 10,
max: 100,
onChanged: (value) {
setState(() {
print(value);
_fontSize = value;
});
},
),
);
}
}
How would I properly access the _runThisFunction(...) within the onTap()?
...
class _DealList extends State<DealList> with AutomaticKeepAliveClientMixin {
void _runThisFunction() async {
print('Run me')
}
#override
Widget build(BuildContext context) {
super.build(context);
return FutureBuilder(
future: _loadingDeals,
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.connectionState == ConnectionState.done
? RefreshIndicator(
onRefresh: _handleRefresh,
child: ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemCount: snapshot.data['deals'].length,
itemBuilder: (context, index) {
final Map deal = snapshot.data['deals'][index];
return _getDealItem(deal, context);
},
),
)
: Center(
child: CircularProgressIndicator(),
);
},
);
}
}
Container _getDealItem(Map deal, context) {
return new Container(
height: 90.0,
child: Material(
child: InkWell(
child: _getDealRow(deal), // <-- this renders the row with the `deal` object
onTap: () {
// Below call fails
// 'The function isn't defined'
_runThisFunction();
},
),
),
);
}
The reason for that is that you are out of scope.
Little hint: The word "function" always indicates that the function you are trying to call is not part of a class and the keyword "method" shows you that the function you are trying to call is part of a class.
In your case, _runThisFunction is defined inside of _DealList, but you are trying to call it from outside.
You either need to move _getDealItem into _DealList or _runThisFunction out.
/// In this case both methods [_runThisFunction()] and [_getDealItem()] are defined inside [_DealList].
class _DealList extends State<DealList> with AutomaticKeepAliveClientMixin {
void _runThisFunction() ...
Container _getDealItem() ...
}
/// In this case both functions are defined globally.
void _runThisFunction() ...
Container _getDealItem() ...
You wil need to make sure that you also apply the same logic to _getDealRow and other nested calls.
Our app is built on top of Scaffold and to this point we have been able to accommodate most of our routing and navigation requirements using the provided calls within NavigatorState (pushNamed(), pushReplacementNamed(), etc.). What we don't want though, is to have any kind of 'push' animation when a user selects an item from our drawer (nav) menu. We want the destination screen from a nav menu click to effectively become the new initial route of the stack. For the moment we are using pushReplacementNamed() for this to ensure no back arrow in the app bar. But, the slide-in-from-the-right animation implies a stack is building.
What is our best option for changing that initial route without animation, and, can we do that while also concurrently animating the drawer closed? Or are we looking at a situation here where we need to move away from Navigator over to just using a single Scaffold and updating the 'body' directly when the user wants to change screens?
We note there is a replace() call on NavigatorState which we assume might be the right place to start looking, but it's unclear how to access our various routes originally set up in new MaterialApp(). Something like replaceNamed() might be in order ;-)
What you're doing sounds somewhat like a BottomNavigationBar, so you might want to consider one of those instead of a Drawer.
However, having a single Scaffold and updating the body when the user taps a drawer item is a totally reasonable approach. You might consider a FadeTransition to change from one body to another.
Or, if you like using Navigator but don't want the default slide animation, you can customize (or disable) the animation by extending MaterialPageRoute. Here's an example of that:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyCustomRoute<T> extends MaterialPageRoute<T> {
MyCustomRoute({ WidgetBuilder builder, RouteSettings settings })
: super(builder: builder, settings: settings);
#override
Widget buildTransitions(BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
if (settings.isInitialRoute)
return child;
// Fades between routes. (If you don't want any animation,
// just return child.)
return new FadeTransition(opacity: animation, child: child);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Navigation example',
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/': return new MyCustomRoute(
builder: (_) => new MyHomePage(),
settings: settings,
);
case '/somewhere': return new MyCustomRoute(
builder: (_) => new Somewhere(),
settings: settings,
);
}
assert(false);
}
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Navigation example'),
),
drawer: new Drawer(
child: new ListView(
children: <Widget> [
new DrawerHeader(
child: new Container(
child: const Text('This is a header'),
),
),
new ListTile(
leading: const Icon(Icons.navigate_next),
title: const Text('Navigate somewhere'),
onTap: () {
Navigator.pushNamed(context, '/somewhere');
},
),
],
),
),
body: new Center(
child: new Text(
'This is a home page.',
),
),
);
}
}
class Somewhere extends StatelessWidget {
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Text(
'Congrats, you did it.',
),
),
appBar: new AppBar(
title: new Text('Somewhere'),
),
drawer: new Drawer(
child: new ListView(
children: <Widget>[
new DrawerHeader(
child: new Container(
child: const Text('This is a header'),
),
),
],
),
),
);
}
}
Use PageRouteBuilder like:
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (_, __, ___) => Screen2(),
transitionDuration: Duration.zero,
),
);
And if you want transition, simply add following property to above PageRouteBuilder, and change seconds to say 1.
transitionsBuilder: (_, a, __, c) => FadeTransition(opacity: a, child: c),