Skip to content

Instantly share code, notes, and snippets.

@loushou
Last active October 31, 2021 18:22
Show Gist options
  • Save loushou/2b3c4e46a7931b40fc15a15069a03cec to your computer and use it in GitHub Desktop.
Save loushou/2b3c4e46a7931b40fc15a15069a03cec to your computer and use it in GitHub Desktop.
Flutter - Programmatically trigger button without directly tapping it #flutter #statemanagement #provider
/*
The goal of this is to show you how to abstract the action of tapping a button,
such that it can be triggered from some other code. In this case we trigger one
button's action from tapping another button... but the principle should carry
over if you are triggering it from somewhere else in the code as well, possibly
not related to a user button tap (maybe after a certain amount of time, or when
another state updates elsewhere in the code).
*/
// this package provides the state management utilities we need for this magic to happen
import 'package:provider/provider.dart';
// base flutter material import
import 'package:flutter/material.dart';
// our other files in this example
import 'my_button.dart';
import 'my_secret_button.dart';
void main() => runApp(Home());
class Home extends StatefulWidget {
@override
HomeState createState() => HomeState();
}
class HomeState extends State<Home> {
// the holds the current state of the action button.
// true == do something like show the menu
// false == do something else like close the menu
ValueNotifier<bool> buttonTrigger;
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Programmatic Trigger Example',
// ChangeNotifierProvider will provide that state handler to child widgets
home: ChangeNotifierProvider<ValueNotifier<bool>>(
builder: (BuildContext context) => buttonTrigger,
// everything below here will have access to the state
child: Container(
color: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
// the button that actually should trigger the action menu
Padding(
padding: const EdgeInsets.all(16),
child: MyButton(),
),
// the other button that will trigger that first button's action menu
Padding(
padding: const EdgeInsets.all(16),
child: MySecretButton(),
),
],
),
),
),
);
}
}
/*
This is the main button. It may trigger an action menu, or trigger a UI change
or whatever. The code that runs on button tap will be handled here, and will
live in this button (or could live elsewhere even).
*/
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
// This example must be Stateful, because it does the actual handling part too.
// if the handling was managed elsewhere, this could be Stateless
class MyButton extends StatefulWidget {
@override
MyButtonState createState() => MyButtonState();
}
class MyButtonState extends State<MyButton> {
@override
Widget build(BuildContext context) {
// Consumer() goes up the try to find the 'type' of ChangeNotifier it is set to consume.
// here it is set to consume a ChangeNotifier of type ValueNotifier<bool>, which matches
// the type of the buttonTigger variable that is 'provided' by our parent widget (main.dart)
return Consumer<ValueNotifier<bool>>(
// parameter #2 contains a reference to the buttonTrigger variable
builder: (BuildContext context, ValueNotifier<bool> buttonTrigger, _) => GestureDetector(
onTap: () {
// on tap, instead of doing some special code here, we simply change the value of
// our ChangeNotifier, buttonTrigger
buttonTrigger.value = true;
},
child: Container(
color: Colors.red,
child: Text('The MAIN button'),
),
),
);
}
// this function does our magic. it finds out the current value of the buttonTrigger, then
// performs an appropriate action based on the value. this could be logging a message,
// like i have done here, or it could be perform a UI change, or update another state value,
// or exit the app, or whatever.
void _handleButtonPress() {
// make sure to wrap this in a try-catch. if you put this button in a tree that is not
// providing the buttonTrigger, then it will throw an exception and crash you
try {
// use Provider.of<type>() to fetch the value we need from up the tree. this will
// traverse up the tree to find the first parent that is providing a ChangeNotifier
// that matches the type you specify. if no such parent exists, exception is thrown
final ValueNotifier<bool> buttonTrigger = Provider.of<ValueNotifier<bool>>(context);
// make sure we have a value. there is are better more accurate ways to do this...
if (buttonTrigger != null) {
// when the value is true... open the action menu (or whatever)
if (buttonTrigger.value) {
print('open action menu');
// otherwise, close the action menu (or whatever
} else {
print('close action menu');
}
}
} catch (e) {}
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
// make sure to wrap this in a try-catch. if you put this button in a tree that is not
// providing the buttonTrigger, then it will throw an exception and crash you
try {
// traverse up the tree to find the closest ValueNotifier<bool> being provided
final ValueNotifier<bool> buttonTrigger = Provider.of<ValueNotifier<bool>>(context);
// if we have a value
if (buttonTrigger != null) {
// if it has listeners already, lets remove dupes, just in case this is called
// multiple times (for instance if we move the widget around the tree)
if (buttonTrigger.hasListeners) {
buttonTrigger.removeListener(_handleButtonPress);
}
// *******************************MOST IMPORTANT LINE*********************************
// add a listener to the value being changed. this is what actually DETECTS and
// RESPONDS to changes in the buttonTrigger variable. this is what says 'did the
// buttonTrigger value change? or lets make stuff happen now.' this is what actually
// calls our button logic
buttonTrigger.addListener(_handleButtonPress);
}
} catch (e) {}
}
}
/*
This button has no state of it's own, and therefore does not have any logic of it's own.
When you tap this, instead of doing it's own thing, it is triggering the other button's
action, through the magic of State Management, using the `provider` package.
*/
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
class MySecretButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Find a ChangeNotifier, by traversing up the tree, that is of type ValueNotifier<bool>.
// use that for our onTap event.
return Consumer<ValueNotifier<bool>>(
builder: (BuildContext context, ValueNotifier<bool> buttonTrigger, _) => GestureDetector(
// tell the other button to run it's code for opening the action menu (or whatever)
onTap: () {
buttonTrigger.value = true;
},
child: Container(
color: Colors.red,
child: Text('Trigger other button action'),
),
),
);
}
}
@NEOALEX00016
Copy link

no work 2021

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment