EDIT (2nd):
I made a change the state from Stateful widgets to Stateless widget, and it turns out, I can solve the problem.
EDIT:
So I made a mistake, I shouldn't be making a widget inside onTap function, Instead, I should've instantiate CardMatcher somewhere and then access CardMatcher, send the button's keyword, and let CardMatcher check the keyword for me when the button is clicked.
Any Idea how to do that? Can someone make a simple code for me?
In other words, I want to make a widget that can check if there are two buttons that have been clicked. That widget, should be in another file so it may be reused.
ーーーー
So I made a custom button that will pass a keyword to another widget (CardMatcher) that will check the keyword. If two buttons have the same keywords, then the widget (CardMatcher) will do something about it.
The button will pass the keyword when it is clicked. Sadly, nothing happens. There's no error detected, but the app didn't build the CardMatcher as well. Here's the code for the button:
import 'package:flutter/material.dart';
import 'package:fluttermatchcard/cardMatcher.dart';
import 'package:fluttermatchcard/cardMatcher.dart';
import 'package:fluttermatchcard/testerState.dart';
class CardButton extends StatefulWidget {
final Widget child;
//final GestureTapCallback onPressed;
final double widthBut;
final double heightBut;
final Color colorInitial;
final Color colorClicked ;
final Color textColorInitial ;
final Color textColorClicked ;
final Alignment alignment;
final Text text;
final String keyword;
CardButton({
//#required this.onPressed,
this.child,
#required this.keyword,
this.heightBut =40,
this.widthBut = 75,
this.colorClicked = Colors.white,
this.colorInitial=Colors.amber,
this.textColorClicked = Colors.amber,
this.textColorInitial = Colors.white,
this.alignment = Alignment.center,
this.text = const Text(
"Card",
style: TextStyle(
fontSize: 20,
),
),
});
#override
_CardButtonState createState() => _CardButtonState(
keyword,
widthBut,
heightBut,
colorClicked,
colorInitial,
textColorClicked,
textColorInitial,
alignment,
text,
);
}
class _CardButtonState extends State<CardButton> {
String _keyword;
double _widthBut ;
double _heightBut;
Color _colorInitial;
Color _colorClicked ;
Color _textColorInitial;
Color _textColorClicked ;
Alignment _alignment ;
Text _text;
_CardButtonState(
this._keyword,
this._widthBut,
this._heightBut,
this._colorClicked,
this._colorInitial,
this._textColorClicked,
this._textColorInitial,
this._alignment,
this._text,
);
Color _colorNow;
Color _textColorNow;
bool isClicked = false;
void initState() {
_colorNow=_colorInitial;
_text = Text(_text.data, style: TextStyle(color: _textColorInitial, fontSize: _text.style.fontSize),);
super.initState();
}
void ChangeButton(){
setState(() {
isClicked= !isClicked;
if(isClicked){
_colorNow=_colorClicked;
_text = Text(_text.data, style: TextStyle(color: _textColorClicked, fontSize: _text.style.fontSize),);
}
else{
_colorNow=_colorInitial;
_text = Text(_text.data, style: TextStyle(color: _textColorInitial, fontSize: _text.style.fontSize),);
}
});
//super.initState();//no idea
}
#override
Widget build(BuildContext context) {
return Container(
width: _widthBut,
height: _heightBut,
child: InkWell(
onTap: (){ChangeButton();
CardMatcher(_keyword);
print("onTap");},
child: Container(
padding: EdgeInsets.all(3),
alignment: _alignment,
decoration: BoxDecoration(
color: _colorNow,
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 5,
offset: Offset(0,2),
spreadRadius: 2
)
],
border: Border.all(
color: Colors.amberAccent
)
),
child: _text,
),
),
);
//widget.onPressed();
}
}
for the CardMatcher:
import 'package:flutter/material.dart';
class CardMatcher extends StatefulWidget {
final String keyword_now;
CardMatcher(this.keyword_now);
#override
_CardMatcherState createState() {
print("cardMatch");
_CardMatcherState(keyword_now);
}
}
class _CardMatcherState extends State<CardMatcher> {
String _keyword_1;
String _keyword_2;
String _keyword_now;
_CardMatcherState(
this._keyword_now,
);
void _collectKeywords(){
print("EnterCollect");
setState(() {
if(_keyword_1==null)
{
print("key1");
_keyword_1=_keyword_now;
}
else{
_keyword_2=_keyword_now;
_matchKeyword(_keyword_1,_keyword_2);
}
});
}
void _matchKeyword(_keyWord_one, _keyWord_two){
if(_keyWord_one==_keyWord_two){
//Lock the But
print("MATCH!!!!");
}
}
#override
Widget build(BuildContext context) {
print("BUILD");
_collectKeywords();
return null;
}
}
Save me, please
You try to create the CardMatcher widget in your onTap function!
What you need to do:
1. onTap must just call ChangeButton (but don't create CardMatcher here)
2. ChangeButton must call setState() AND change the _keyword field
3. Use a CardMatcher instance in your build tree with _keyword as constructor parameter
When calling setState, you indicate to flutter to rebuild (i.e. call build()) the widget. If you change a state (i.e. _keyword), then the build method will use the new state's value to build the widget accordling
The only thing that seems to be missing in your code is for you to use the widget. prefix to access the Stateful widgets constructor variables, like this:
class CardMatcher extends StatefulWidget {
final String keyword_now;
CardMatcher(this.keyword_now);
#override
_CardMatcherState createState() {
print("cardMatch");
_CardMatcherState(keyword_now);
}
}
class _CardMatcherState extends State<CardMatcher> {
String _keyword_1;
String _keyword_2;
void _collectKeywords(){
print("EnterCollect");
setState(() {
if(_keyword_1 == null)
{
print("key1");
_keyword_1 = widget.keyword_now;
}
else{
_keyword_2 = widget.keyword_now;
_matchKeyword(_keyword_1,_keyword_2);
}
});
}
void _matchKeyword(_keyWord_one, _keyWord_two){
if(_keyWord_one==_keyWord_two){
//Lock the But
print("MATCH!!!!");
}
}
#override
Widget build(BuildContext context) {
print("BUILD");
_collectKeywords();
return null;
}
}
Related
I have problems following step by step what happens when onChanged is triggered on my TextField. Especially, I have a problem understanding where and why the variable value gets its actual value in the following example.
Example:
class felder extends StatefulWidget {
felder({super.key});
String textFieldName = "";
#override
State<felder> createState() => _felderState();
}
class _felderState extends State<felder> {
#override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
obscureText: false,
decoration: const InputDecoration(
border: OutlineInputBorder(), labelText: 'Name'),
onChanged: (value) => widget.textFieldName = value,
)
],
);
}
}
How I always imagined it: I think flutter passes a function in the background, which has a parameter value, that has the content of the TextField.
Actually TextField is a widget that has its own state.
Whenever user types something, the value in a TextField
changes.
At that time, a callback is fired from the TextField.
The changed value is also passed along with the
callback.
Using onChanged: (value){ print(value); } , we can
get the value from that callback and use it as per our needs.
From TextField source code,
The text field calls the [onChanged] callback whenever the user changes the text in the field. If the user indicates that they are done typing in the field (e.g., by pressing a button on the soft keyboard), the text field calls the [onSubmitted] callback.
To get the value from a TextField, you can also use TexteditingController.
First declare TextEditingController controller = TextEditingController();.
Then inside your TextField, add the controller like this
TextField(
controller: controller,
),
Then to get the value from controller, you can use controller.value.text.
What is a callback?
From GeeksForGeeks:
Callback is basically a function or a method that we pass as an
argument into another function or a method to perform an action. In
the simplest words, we can say that Callback or VoidCallback are used
while sending data from one method to another and vice-versa
Creating a callback
To create your own callback, you can use ValueChanged.
Code example:
Let's create our own button, that when the onChanged is called, it will give us a new value:
class ButtonChange extends StatefulWidget {
final bool value;
final ValueChanged<bool> onChanged;
ButtonChange({Key? key, required this.value, required this.onChanged})
: super(key: key);
#override
State<ButtonChange> createState() => _ButtonChangeState();
}
class _ButtonChangeState extends State<ButtonChange> {
bool _isToggled = false;
void toggle() {
setState(() {
_isToggled = !_isToggled;
});
widget.onChanged(_isToggled);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: toggle,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: _isToggled ? Colors.green : Colors.red,
borderRadius: BorderRadius.circular(50),
),
),
);
}
}
Usage:
class MyWidget extends StatefulWidget {
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var _value = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
ButtonChange(
value: _value,
onChanged: (bool value) => setState(() {
_value = value;
})),
Text('$_value')
],
);
}
}
Complete example
You can run/paste this example in your editor, and take a look:
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var _value = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
ButtonChange(
value: _value,
onChanged: (bool value) => setState(() {
_value = value;
})),
Text('$_value')
],
);
}
}
class ButtonChange extends StatefulWidget {
final bool value;
final ValueChanged<bool> onChanged;
ButtonChange({Key? key, required this.value, required this.onChanged})
: super(key: key);
#override
State<ButtonChange> createState() => _ButtonChangeState();
}
class _ButtonChangeState extends State<ButtonChange> {
bool _isToggled = false;
void toggle() {
setState(() {
_isToggled = !_isToggled;
});
widget.onChanged(_isToggled);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: toggle,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: _isToggled ? Colors.green : Colors.red,
borderRadius: BorderRadius.circular(50),
),
),
);
}
}
See also
How to pass callback in Flutter
What's in onChanged Docs ?
ValueChanged<String>? onChanged
onChanged is of type ValueChanged<String> and is called when the user initiates a change to the TextField's value: when they have inserted or deleted text.
This callback doesn't run when the TextField's text is changed programmatically, via the TextField's controller. Typically it isn't necessary to be notified of such changes, since they're initiated by the app itself.
What is Callback ?
If we go by definition, the Callback is a function or a method which we pass as an argument into another function or method and can perform an action when we require it.
For Example, if you are working in any app and if you want any change in any value then what would you do?
Here you are in a dilemma that what you want to change either state() or a simple value/values. If you need to change states then you have various state-changing techniques but if you want to change simple values then you will use Callback.
Refer this article to understand the callback on event of textChange this will surely make you understand the core behind the mechanism
I am creating a custom button widget MyButton by composing ElevatedButton that will contains a Text widget as Child. It has a boolean isLoading property that will be used to configure the type of Button to build. When isLoading = true it will return a grey loading button having width same as the width of text provided prior to this loading button build. My question is how can I get the width of child Text widget before the build method is called or invoked again by setState, so that I can use that width to create the loading button of same size?
Loading Button will always be called after default Button has been built with child Text widget. (i.e. isLoading can be true only when widget is rebuilding not initially).
Here's the code:
class MyButton extends StatefulWidget {
final String text;
final bool isLoading;
final VoidCallback onPressed;
MyButton({Key key, #required this.text, #required this.isLoading, this.onPressed}) : super(key: key);
#override
State<StatefulWidget> createState() => _MyButtonState();
}
class _MyButtonState extends State<MyButton> {
double _width; // Width of the [Text] child widget that will be used to get the [Container] of same width for loading button
#override
void initState() {
super.initState();
_width = 150;
}
#override
void didUpdateWidget(covariant MyButton oldWidget) {
super.didUpdateWidget(oldWidget);
/*
Calculate the width....
*/
}
#override
Widget build(BuildContext context) {
// I want to get the width of this child
Widget child = Text(
widget.text,
style: TextStyle(color: Colors.black),
);
if (widget.isLoading) {
child = Container(
height: 60,
width: _width, // this should be same as [Text] width
color: Colors.grey,
);
}
return ElevatedButton(
child: child,
onPressed: !widget.isLoading ? widget.onPressed : null,
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(
!widget.isLoading ? Colors.red : Colors.grey,
),
),
);
}
}
Here is the demo of what I want to do but size of button when isLoading = true is absolute for now.
I have checked other posts and questions but could not find useful way to solve my usecase. Using GlobalKey is a solution but is not the recommended way and using RenderObject I was not able to solve this as in that case we can only get the intrinsic heights and widths of the parent.
I think there might be some other way to do this other than using GlobalKey and RenderObject.
Rather than creating a Container for that I would prefer you just hide the text so that size of button will be same as previous. Try below code :
Widget build(BuildContext context) {
return ElevatedButton(
child: Opacity(
opacity: !widget.isLoading ? 1 : 0,
child: Text(
widget.text,
style: TextStyle(color: Colors.black),
),
),
onPressed: !widget.isLoading ? widget.onPressed : null,
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(
!widget.isLoading ? Colors.red : Colors.grey,
),
),
);
}
We can use the add postFrameCallback to WidgetBinding instance to get the size of the widget from the context inside postFrameCallback.
NOTE: This may not be the efficient appraoch.
Here's the code that worked in my case.
#override
void initState() {
super.initState();
getWidth();
_width = _width ?? 150;
}
void getWidth() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (context?.size is Size) {
_width = context.size.width;
print(context.size.toString());
}
});
}
#override
void didUpdateWidget(covariant MyButton oldWidget) {
getWidth();
super.didUpdateWidget(oldWidget);
}
I'm using the flutter url_launcher https://pub.dev/packages/url_launcher package to open urls when i click some button.
With the new Link widget im now able to open a web page on the same tab but i cant add mouse pointer when user is hovering the button
import 'package:bianca/UI/botao_azul.dart';
import 'package:url_launcher/link.dart';
import 'package:flutter/material.dart';
String link = "https://www.google.com";
class MesmaAba extends StatelessWidget {
final double tamanho;
final String conteudo;
MesmaAba({this.tamanho, this.conteudo});
#override
Widget build(BuildContext context) {
return Link(
uri: Uri.parse(link),
builder: (BuildContext context, FollowLink followLink) => BotaoAzul(
conteudo: conteudo,
tamanho: tamanho,
funcao: followLink
),
);
}
}
BotaoAzul class:
import 'package:flutter/material.dart';
class BotaoAzul extends StatelessWidget {
final String conteudo;
final double tamanho;
final Function funcao;
BotaoAzul({this.conteudo, this.tamanho,this.funcao});
#override
Widget build(BuildContext context) {
return Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: FlatButton(
onPressed: funcao,
child: Text(conteudo,
style: TextStyle(
fontSize: tamanho,
color: Colors.white,
fontWeight: FontWeight.bold))),
),
decoration: BoxDecoration(
color: Colors.blue[900], borderRadius: BorderRadius.circular(20.0)),
);
}
}
I can already open urls with botaoAzul button on another tab using this function (and without the Link widget, the mouse changes on hovering the button)
import 'package:url_launcher/url_launcher.dart';
void launchLink(String link) async {
await launch(
link,
);
}
But i need to open the url on the same tab.
I've already tried all implementations of this other question without success:
https://stackoverflow.com/questions/56211844/flutter-web-mouse-hover-change-cursor-to-pointer
As I know latest version of flutter web supports hand cursor for InkWell widget automatically. Below simple class:
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
/// Provides an anchor link to web URL.
class HoveredWebAnchor extends StatefulWidget {
HoveredWebAnchor(
{Key key,
#required this.label,
#required this.url,
this.underlined = true})
: assert(label != null),
assert(url != null),
assert(underlined != null),
super(key: key);
/// The label of anchor
final String label;
/// The web URL to open when anchor clicked
final String url;
/// Identifies if anchor label will be underlined.
final bool underlined;
#override
_HoveredWebAnchorState createState() => _HoveredWebAnchorState();
}
class _HoveredWebAnchorState extends State<HoveredWebAnchor> {
/// Current text style
TextStyle _textStyle;
#override
Widget build(BuildContext context) {
return InkWell(
hoverColor: Colors.transparent,
child: Text(
widget.label,
style: _textStyle,
),
onHover: (hovered) {
setState(() {
if (hovered) {
_textStyle = TextStyle(color: Theme.of(context).accentColor);
if (widget.underlined) {
_textStyle = _textStyle.copyWith(
decoration: TextDecoration.underline,
);
}
} else {
_textStyle = null;
}
});
},
onTap: () {
launch(widget.url, forceWebView: true);
},
);
}
}
Using:
HoveredWebAnchor(
label: 'Open Google',
url: 'http://www.google.com',
),
I have improved suggestion of #BambinoUA to sound null safety and some minor changes so I decided to share it with y'all
class HoveredWebAnchor extends StatefulWidget {
const HoveredWebAnchor(
this.label, {
Key? key,
required this.style,
this.maxLines,
required this.onTap,
}) : super(key: key);
final String label;
final TextStyle? style;
final int? maxLines;
final VoidCallback onTap;
#override
_HoveredWebAnchorState createState() => _HoveredWebAnchorState();
}
class _HoveredWebAnchorState extends State<HoveredWebAnchor> {
TextStyle? _textStyle;
#override
void initState() {
_textStyle = widget.style;
super.initState();
}
#override
Widget build(BuildContext context) {
return InkWell(
hoverColor: Colors.transparent,
onHover: (hovered) {
setState(() {
if (hovered) {
_textStyle = _textStyle?.copyWith(
decoration: TextDecoration.underline,
);
} else {
_textStyle = _textStyle?.copyWith(
decoration: widget.style?.decoration,
);
}
});
},
onTap: widget.onTap,
child: Text(
widget.label,
style: _textStyle,
maxLines: widget.maxLines,
),
);
}
}
The way to change your mouse cursor whilst keeping the behavior of the Link Widget the same would be to wrap the Link Widget in a MouseRegion
MouseRegion(
cursor: SystemMouseCursors.click,
child: Link(
uri: Uri.parse(link),
builder: (BuildContext context, FollowLink followLink) =>
BotaoAzul(
conteudo: conteudo,
tamanho: tamanho,
funcao: followLink
),
),
)
From the Link widget revision 2 document:
The Link widget doesn’t provide any mouse cursor, and fully relies on the user to do their own mouse cursor. In many cases, users will be using a button, which already shows the correct mouse cursor. In other cases, the user can wrap the Link (or the child of the Link) in a mouse region and give it a cursor.
found something last night that solves the problem:
Instead of using url_launcher Link, i'm now importing the html package
import 'dart:html' as html;
String link = "https://www.google.com";
.....
void openPage(){
html.window.location.assign(link);
}
...... (widget build method)
BotaoAzul(
conteudo: "Hey",
tamanho: 30,
funcao: openPage
),
It now opens the link on the same tab and i can return to my flutter app from the chrome back button
I used the example below to test and learn how to call parent functions from child widget.
When you click the TestButton, the countRefresh function is called and the variable count increases by 1.
Right now the button changes color each time it is clicked (either blue or red).
QUESTION: say that I want the color to change based on some logic around the count variable, how can I access the count variable from within the TestButton widget?
E.g. if count is a multiple of three then the button should be red, otherwise blue.
I read about InheritedWidgets, but it seems like variables must be final inside InheritedWidgets (if I don't put final before int count = 0; I get the 'this class is marked as immutable' message error).
But based on this example I need count to change each time the button is clicked.
What's the alternative to InheritedWidgets?
import 'package:flutter/material.dart';
class Test extends StatefulWidget {
static const String id = 'test';
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
int count = 0;
Color color = Colors.red;
void refreshCount() {
setState(() {
count += 1;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child:
Text('The button was pressed $count time${count == 1 ? '' : 's'}'),
),
floatingActionButton: TestButton(
color: color,
notifyParent: refreshCount,
),
);
}
}
class TestButton extends StatefulWidget {
TestButton({
#required this.color,
#required this.notifyParent,
});
final Color color;
final void Function() notifyParent;
#override
_TestButtonState createState() => _TestButtonState();
}
class _TestButtonState extends State<TestButton> {
Color color;
void initState() {
color = widget.color;
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
widget.notifyParent();
setState(() {
color = color == Colors.red ? Colors.blue : Colors.red;
});
},
child: Container(
child: Icon(
Icons.add,
size: 80,
),
color: color,
),
);
}
}
I want the color to change based on some logic around the count variable, how can I access the count variable from within the TestButton widget? E.g. if count is a multiple of three then the button should be red, otherwise blue.
You need to pass count down to the child and let the child to build itself based on that value.
Don't try to access the parent widget. In declarative programming you can only update states and rebuild trees.
Here's a good video about state management in Flutter: https://www.youtube.com/watch?v=HrBiNHEqSYU
I am new to Flutter so I am not sure if this is possible...
I am trying to customise a statefulWidget, building upon the MaterialDesignIcon RaisedButton.
I would like to simply pass in two parameters on the instantiation of the raisedButton.
So when I create the RaisedButton I can do something like below.....
RaisedButton(backgroundColor: Colors.grey, text: 'Press me')
Please see the code I am trying to make work below.
class CustomRaisedButton extends StatefulWidget {
#override
CustomRaisedButtonState createState() => CustomRaisedButtonState();
}
void buttonPressed() {
print('A FUNCTION WOULD GO HERE');
}
class CustomRaisedButtonState extends State<CustomRaisedButton> {
var _backgroundColor = Colors.transparent;
var _text = String;
var _hoverColor = Colors.transparent;
#override
void initState(backgroundColor, text) { < ---- // Can I put the parameters required here?
super.initState();
this._backgroundColor = backgroundColor;
this._text = text;
if (_backgroundColor != Colors.grey) {
_textColor = Colors.white;
}
if (_backgroundColor == Colors.grey) {
_hoverColor = Colors.black54;
}
if (_backgroundColor == Colors.red) {
_hoverColor = Colors.deepOrangeAccent;
}
if (_backgroundColor == Colors.lightGreen) {
_hoverColor = Colors.green;
}
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return RaisedButton(
onPressed: buttonPressed,
color: _backgroundColor,
textColor: Colors.black,
disabledColor: Colors.black38,
disabledTextColor: _textColor,
disabledElevation: 4,
elevation: 4,
hoverColor: _hoverColor,
padding: const EdgeInsets.all(8.0),
child: Text('$_text', style: CustomTextStyle.display1(context))
);
}
}
This may seem a stupid question as I know you can access those properties in the parameters of the custom widget instantiation anyway. But I would like to change the different properties of the button depending on the backgroundColor. Thanks in advance.
You can access the widgets properties in a stateful widget using widget.:
class ExampleWidget extends StatefulWidget {
final String data;
const ExampleWidget({Key key, this.data}) : super(key: key);
#override
_ExampleWidgetState createState() => _ExampleWidgetState();
}
class _ExampleWidgetState extends State<ExampleWidget> {
String text;
#override
void initState() {
super.initState();
text = widget.data.substring(0,2).toUpperCase();
}
#override
Widget build(BuildContext context) {
return Text(text);
}
}
no question is stupid! :D. There is not this possibility, initState doesn't receive any parameters. But as you said yourself you can always access the Widget properties.
What do you want to accomplish that you are not able to using the widgets properties approach?