Flutter: Programatically perform a button click - flutter

How can you programatically perform a button click in Flutter? I basically want to bind a number of buttons to keys on the keyboard, the most important part is that after a key is pressed the corresponding button the visual state of the button is triggered to inform the user which button was pressed.
I am aware you can invoke the onTap function, but that by itself doesn't update the visual state of button. Is there a way to simulate this kind of behavior, I tried to look into MaterialStateController but doesn't feel like it's the right approach.

I was going to say that you could use the MaterialStateController to simulate that the button has been pressed.
This can be done by updating the state controller in some good enough fashion of your choice. Something like this perhaps:
Non-optimized code for this (you will bind to keys on the keyboard instead):
ElevatedButton(
onPressed: () async {
button2StatesController.update(MaterialState.pressed, true);
await Future.delayed(const Duration(milliseconds: 200));
button2StatesController.update(MaterialState.pressed, false);
},
child: const Text('Button 1'),
),
ElevatedButton(
onPressed: () {},
statesController: button2StatesController,
child: const Text('Button 2'),
)
You cannot simulate the InkWell effect (as far as I know) without more intervention. Like creating your own button with your own inkwell animations etc...

I discovered that _InkWellResponseState (internal-) classes have a function called simulateTap. The function is bound against ActivateIntent and by default this intend is fired whenever the user presses Enter or Space
class SimActivateIntent extends ActivateIntent {
const SimActivateIntent (this.model);
final FocusNode model;
}
class SimActivateAction extends Action<SimActivateIntent> {
SimActivateAction ();
#override
void invoke(covariant SimActivateIntentintent) {
Actions.maybeInvoke(intent.model.context!, const ActivateIntent());
}
}
With the intends listed above one can invoke the ActivateIntent on a specific object and simulate a button press. They can bound into the application using Shortcuts & Actions classes.
Map<ShortcutActivator, Intent> get defaultShortcuts {
return <ShortcutActivator, Intent>{
const SingleActivator(LogicalKeyboardKey.backspace):
SimActivateIntent(digitBackspace),
}
}
#override
Widget build(BuildContext context)
{
return Shortcuts(
shortcuts: defaultShortcuts,
child: Actions(
actions: <Type, Action<Intent>>{
SimActivateIntent: SimActivateAction(),
},
child: ...
));
}

Related

Flutter Tooltip on One Tap / Hold Down

My app has several textfields and I want to have a tooltip so that users know can see the definition of each field.
I came across this answer, but it wasn't helpful: Flutter Tooltip on One Tap. Therefore I decided to try and fix it myself.
Here is how to do it:
First add GestureDetector as child for Tooltip,
TooltipTriggerMode.manual for triggerMode.
add onTapDown, onTapUp, and onTapCancel as follows
Widget build(BuildContext context) {
final tooltipkey = GlobalKey<TooltipState>();
return Tooltip(
key: tooltipkey,
message: message,
triggerMode: TooltipTriggerMode.manual, // make it manual
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (_) => _onTapDown(tooltipkey), // add this
onTapUp: (_) => _onTapUpAndCancel(tooltipkey), // add this
onTapCancel: () => _onTapUpAndCancel(tooltipkey), // add this
child: Icon(EvaIcons.questionMarkCircleOutline),
),
);
}
and the helper functions shown inside the code above:
void _onTapDown(GlobalKey<TooltipState> tooltipkey) {
tooltipkey.currentState?.ensureTooltipVisible();
}
void _onTapUpAndCancel(GlobalKey<TooltipState> tooltipkey) {
tooltipkey.currentState?.deactivate();
}
Hooray, it works. Now you can hold down the icon to display the tooltip immediately instead of holding it down for a while (the default configuration of tooltip).

Flutter : How to close alertdialog from another file

I have 2 dart file. I want close the alertdialog from another dart file in specific conditions.
How can I ?
for example the function i want.(I wrote to express myself better.)
void example(){
if(control==false){
alertDialog.close();
}
Alert dialog file
class AlertForm extends StatefulWidget {
#override
_AlertFormState createState() => _AlertFormState();
}
class _AlertFormState extends State<AlertForm> {
void _showDialog() {
// flutter defined function
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text("Alert Dialog title"),
content: new Text("Alert Dialog body"),
actions: <Widget>[
// usually buttons at the bottom of the dialog
new FlatButton(
child: new Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
So what I want is to use the pop action in a function in another flutter file.
Call Navigator.of(context).pop(); inside the if block (of the void example function of the other Dart file) and the alert should close.
But here is the problem, the context above might not be part of or aware of the dialog and the above may not work.
More info about your setup would suggest a solution to this problem. Like how exactly does the other Dart file exist relative to the file where _showDialog is declared? If it's something you can easily provide context from AlertForm Dart file to the closingDialog Dart file, then you should know what to do. If that's not the case then a navigator key should help.
You can keep a navigator key for the entire application. Then instead of closing the dialog with Navigator.of(context).pop();, you use navigatorKey.currentState!.pop();.
You should have properly setup the navigator key yourself. Refer to this Stackoverflow answer for how to do that: https://stackoverflow.com/a/72945522/13644299
It is not compulsory to use get_it package. You can simply export the NavigationService (that will keep the navigatorKey) from any Dart file or with any method you deem fit, depending on your current state management architecture.

Finds Element without key

i'm still new in using flutter driver in testing, but as far as i know there are few identifiers that we can use to locate / identify elements, like By Text, By Type, etc
But the problem is, the app that i want to test doesn't have the identifier that i can use to locate them (please correct me if i'm wrong).. the widget code of the app looks like this
Widget _buildNextButton() {
return Align(
alignment: Alignment.bottomRight,
child: Container(
child: IconButton(
icon: Icon(Icons.arrow_forward),
onPressed: () => _controller.nextPage(),
),
),
);
}
where that widget is on a class that extends StatefulWidget.
How can i locate that icon in my test script and click it? can i use something like this? And what type of finder should i use? (byValueKey? bySemanticLabel? byType? or what?)
static final arrowKey = find.byValueKey(LoginKey.nextButton);
TestDriverUtil.tap(driver, arrowKey);
We have text and value checks here in Flutter Driver but if you don't have that you can always go the the hierarchy of app.
what I mean by hierarchy is so button has fix or specific parent right?
Let's take your example here, We have Align > Container > IconButton > Icon widget hierarchy which will not be true for others like there might be IconButton but not with the Container parent.
or StreamBuilder or anything that we can think of.
Widget _buildNextButton() {
return Align(
alignment: Alignment.bottomRight,
child: Container(
child: IconButton(
icon: Icon(Icons.arrow_forward),
onPressed: () => print("clicked button"),
),
),
);
}
This hierarchy should be atleast ideal for top bottom or bottom top approach.
Now what I mean by Top to bottom approach is Align must have IconButton and for bottom to up approach we are saying IconButton must have Align widget as parent.
Here i have taken top down approach so what I'm checking from below code is finding IconButton who is decendent of Align Widget.
also i added firstMatchOnly true as I was checking what happens if same hierarchy appears for both so
test('IconButton find and tap test', () async {
var findIconButton = find.descendant(of: find.byType("Align"), matching: find.byType("IconButton"), firstMatchOnly: true);
await driver.waitFor(findIconButton);
await driver.tap(findIconButton);
await Future.delayed(Duration(seconds: 3));
});
to check for multiple IconButtons with same Align as parent, we need to have some difference like parent should be having Text view or other widget.
find.descendant(of: find.ancestor(
of: find.byValue("somevalue"),
matching: find.byType("CustomWidgetClass")), matching: find.byType("IconButton"), firstMatchOnly: true)
usually I go something like above where I have split the code in seperate file and check for that widget.
But ultimately find something unique about that widget and you can work on that.
**In Lib directory dart class for connecting that widget**
class Testing extends StatelessWidget {
Testing();
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: YourClass(), // Next button containing class that need to test
);
}
}
**In Test directory**
testWidgets('Next widget field test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(Testing());
// find Widget
var buttonFind = find.byIcon(Icons.arrow_forward);
expect(buttonFind, findsOneWidget);
IconButton iconButton = tester.firstWidget(buttonFind);
expect(iconButton.color, Colors.blue);
});

which widget can be used to explain functionality in app

I want to explain something in my app and add a widget which looks like a notification or chat. I want this widget to be visible for some time and then get dismissed. I tried using tooltip but it is visible only when I click it.
Which widget can I use?
The Dart package intro_views_flutter is what you need, but one of its main limitations is that it is displayed on full screen, if that is not an issue to you, then you should take a look at it. Or you can use a showDialog method inside a Future function this way :
Future showNotification() async {
showDialog<String>(
context: context,
child: new AlertDialog(
title: Text('Note!') ,
contentPadding: const EdgeInsets.all(16.0),
content: //any widget you want to display here
),
);
await new Future.delayed(const Duration(seconds: 5), () {
Navigator.of(context).pop(); // this will dismiss the dialog automatically after five seconds
}
}
then when you need it call:
showNotificaion();

InkWell and GestureDetector, how to make them work?

I'd like to use a GestureDetector for it's onTapDown callback but also have a nice InkWell splash effect.
Is it possible to use these two together?
If you want to unconditionally handle the pointer down event with no gesture disambiguation, you can make the InkWell a child of a Listener, and set the onPointerDown handler.
For example:
new Listener(
onPointerDown: (e) { print('onPointerDown'); },
child: new InkWell(
child: new Text('Tap me'),
onTap: () { print('onTap'); }
),
),
It might make sense to add an onTapDown handler to InkWell.
You can pass a HitTestBehavior to GestureDetector to make it "non-blocking" by setting the value to "translucent"
Or you can also trigger it yourself using Material.of(context)
InkWell uses a GestureDetector under it's hood:
https://github.com/flutter/flutter/blob/79b5e5bc8af7d9df3374dfe6141653848d1c03ac/packages/flutter/lib/src/material/ink_well.dart#L560
The reason why a GestureDetector isn't able to work well if the InkWell-Widget is, that it sets the behavior on it's internal GestureDetector to "HitTestBehavior.opaque". This prevents "event bubbling" / event capturing on parent widgets (if my understanding is correct). And because the "behavior" is final, we can't change / fix that by our own.
https://github.com/flutter/flutter/blob/79b5e5bc8af7d9df3374dfe6141653848d1c03ac/packages/flutter/lib/src/material/ink_well.dart#L566
As I mentioned in a comment above, I wrapped the InkWell within a widget (which handles other stuff too) and provided a way to pass a callback into it which is executed on the desired event.
This is my example solution:
import 'package:flutter/material.dart';
class CardPreview extends StatefulWidget {
final dynamic data;
final VoidCallback onTap;
const CardPreview(this.data, this.onTap);
CardPreview._(this.data, this.onTap);
factory CardPreview.fromData(dynamic data, VoidCallback onTap) {
return new CardPreview(data, onTap);
}
CardPreview createState() => new CardPreview(this.data, this.onTap);
}
class CardPreviewState extends State<CardPreview> {
final dynamic data;
final VoidCallback onTap;
CardPreviewState(this.data, this.onTap);
Widget buildCard() {
return Material(
color: Colors.transparent,
child: Ink(
child: InkWell(
child: null,
onTap: () {
if (this.onTap == null) {
return;
}
this.onTap();
},
),
),
);
}
#override
Widget build(BuildContext context) {
return Container(
child: buildCard(),
);
}
}
Building on RĂ©mi's suggestion of adding a HitTestBehavior.translucent behavior to your GestureDetector, this is how I would solve your issue:
Material(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTapDown: (details) {
//Do something
},
child: InkWell(
onTap: () {},
child: Container(height: 20.0, width: 20.0, color: Colors.red),
),
),
),
I was able to use something similar to essentially add a onLongPressStart and onLongPressEnd to my InkWell.
InkWell has onTapDown callback now
https://api.flutter.dev/flutter/material/InkResponse/onTapDown.html
https://api.flutter.dev/flutter/material/InkWell/InkWell.html
But, GestureDetector still has more functionality than InkWell, e.g. onSecondaryTap. Thus nesting InkWell and GestureDetector is useful today.
HitTestBehavior.opaque has no impact on child-parent relationship
https://github.com/flutter/flutter/issues/18450#issuecomment-397372078
https://github.com/flutter/flutter/issues/74733#issuecomment-767859584
In short, HitTestBehavior.opaque only prevent sibling behind it from receiving events by return true in HitTest so that its direct parent won't pass on the event to its next child(in reversed order) and return true in HitTest.(Thus, sibling behind its ancestors won't receive events as well. Just like that its ancestors' behaviors are overwritten to opaque.) BUT its ancestors always DO receive events!
Remark: If A is inside B(i.e. A is a descendant of B), then B will never be behind A.
Related source:
https://api.flutter.dev/flutter/rendering/RenderBox/hitTest.html
https://api.flutter.dev/flutter/rendering/RenderProxyBoxWithHitTestBehavior/hitTest.html (RenderProxyBoxWithHitTestBehavior is used by _RenderColoredBox, which is the "color" of Container)
https://api.flutter.dev/flutter/rendering/RenderProxyBoxWithHitTestBehavior/hitTestSelf.html
https://api.flutter.dev/flutter/rendering/RenderBoxContainerDefaultsMixin/defaultHitTestChildren.html
https://api.flutter.dev/flutter/rendering/RenderStack/hitTestChildren.html (Use defaultHitTestChildren)
Acknowledgment: heavily refer to https://github.com/flutter/flutter/issues/18450#issuecomment-601865975
For onTap, only the callback of the "winner" can fire
See https://api.flutter.dev/flutter/widgets/GestureDetector-class.html
Setting GestureDetector.behavior to HitTestBehavior.opaque or HitTestBehavior.translucent has no impact on parent-child relationships: both GestureDetectors send a GestureRecognizer into the gesture arena, only one wins.
Some callbacks (e.g. onTapDown) can fire before a recognizer wins the arena, and others (e.g. onTapCancel) fire even when it loses the arena. Therefore, the parent detector in the example above may call some of its callbacks even though it loses in the arena.
This is tricky:
If winner has onTap or onTapDown, then loser's onTap will never fire, and onTapDown won't fire if the tap is too short such that gesture arena sweeping happens before onTapDown firing.
InkWell has onTap and onTapDown
https://github.com/flutter/flutter/blob/468166c713984941c0e5432b7499a1a05a0a0f61/packages/flutter/lib/src/material/ink_well.dart#L1209-L1219
Thus, to achieve best experience, all primary tap callback should be registered on InkWell. Otherwise, if GestureDetector wins, short tap won't trigger splash, else if InkWell wins, short tap won't trigger Gesture's onTapDown.