Passing paramaters to initState Flutter/Dart? - flutter

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?

Related

Flutter centralized/common loading screen for entire Application

I am working in Riverpod Auth flow boilerplate application.
I want to use common loading screen for all async function even login and logout. Currently I have AppState provider if Appstate loading i show loading screen. it's working fine for login but i wonder it’s good way or bad way.
Can i use this loading screen for all async task in the App?
AuthWidget:
class AuthWidget extends ConsumerWidget {
const AuthWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
AppState appState = ref.watch(appStateProvider);
if(appState.isLoading){
return const Center(child: CircularProgressIndicator(color: Colors.red),);
}
return appState.isAuthenticated ? const HomePage() : const SignIn();
}
}
AppState:
class AppState {
User? user;
bool isLoading;
bool isAuthenticated;
AppState(this.user, this.isLoading, this.isAuthenticated);
}
AuthRepository:
class AuthRepository extends StateNotifier<AppState>{
AuthRepository() : super(AppState(null,false,false));
Future<void> signIn()async {
state = AppState(null,true,false);
await Future.delayed(const Duration(seconds: 3));
User user = User(userName: 'FakeUser', email: 'user#gmail.com');
AppState appState = AppState(user, false, true);
state = appState;
}
}
final appStateProvider = StateNotifierProvider<AuthRepository,AppState>((ref){
return AuthRepository();
});
To answer your question : Yes you can.
The only thing I'd change here is the content of your AppState : I'd use a LoadingState dedicated to trigger your Loader instead.
Here is how I like to manage screens with a common loader in my apps.
1 - Create a LoadingState and provide it
final loadingStateProvider = ChangeNotifierProvider((ref) => LoadingState());
class LoadingState extends ChangeNotifier {
bool isLoading = false;
void startLoader() {
if (!isLoading) {
isLoading = true;
notifyListeners();
}
}
void stopLoader() {
if (isLoading) {
isLoading = false;
notifyListeners();
}
}
}
2 - Define a base page with the "common" loader
class LoadingContainer extends ConsumerWidget {
const LoadingContainer({
Key? key,
required this.child,
}) : super(key: key);
final Widget child;
#override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(loadingStateProvider);
return Stack(
children: [
child,
if (state.isLoading)
const Center(child: CircularProgressIndicator())
else
const SizedBox(),
],
);
}
}
3 - Implement this widget whenever I need to handle loading datas.
return Scaffold(
backgroundColor: AppColor.blue,
body: LoadingContainer(
child: ...
And then I simply have to update my loadingStateProvider and it's isLoading value from a Controller or the Widget directly
If you want a centralized/common async calls, the InheritedWidget is ideal for that, you can just add a method and call it from anywhere down stream and because the call is offloaded with async, you can attach extra arguments and add usefull functionality such as a live update instead of relying on stuff like .then(). This example might not be as simple as FDuhen's but you can mix them together if you want to not use keys
AppState now is a widget and contains trigers that rely on global keys to rebuild the correct components, here i assumed that you actualy want to have an common overlay and not a loading screen widget, if not using a Navigator would be batter
Using keys is specially good if you end up implementing something this line, <token> been just a number that references a group of widgets
key: AppState.of(ctx).rebuild_on_triger(<token>)
class App_State_Data {
GlobalKey? page_key;
bool is_logged = false;
bool loading_overlay = false;
String loading_message = '';
}
class AppState extends InheritedWidget {
final App_State_Data _state;
bool get is_logged => _state.is_logged;
bool get should_overlay => _state.loading_overlay;
String get loading_message => _state.loading_message;
void page_rebuild() {
(_state.page_key!.currentState as _Page_Base).rebuild();
}
GlobalKey get page_key {
if (_state.page_key == null) {
_state.page_key = GlobalKey();
}
return _state.page_key!;
}
void place_overlay(String msg) {
_state.loading_message = msg;
_state.loading_overlay = true;
page_rebuild();
}
void clear_overlay() {
_state.loading_message = '';
_state.loading_overlay = false;
page_rebuild();
}
Future<void> triger_login(String message) async {
place_overlay(message);
await Future.delayed(const Duration(seconds: 2));
_state.is_logged = true;
clear_overlay();
}
Future<void> triger_logout(String message) async {
place_overlay(message);
await Future.delayed(const Duration(seconds: 1));
_state.is_logged = false;
clear_overlay();
}
AppState({Key? key, required Widget child})
: this._state = App_State_Data(),
super(key: key, child: child);
static AppState of(BuildContext ctx) {
final AppState? ret = ctx.dependOnInheritedWidgetOfExactType<AppState>();
assert(ret != null, 'No AppState found!');
return ret!;
}
#override
bool updateShouldNotify(AppState old) => true;
}
Here i added it as the topmost element making it like a global data class with is not necessary, you can split the state content and add just the necessary to where its needed
void main() => runApp(AppState(child: App()));
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
#override
Widget build(BuildContext ctx) {
return MaterialApp(
home: Scaffold(
body: Page_Base(
key: AppState.of(ctx).page_key,
),
),
);
}
}
class Page_Base extends StatefulWidget {
final GlobalKey key;
const Page_Base({
required this.key,
}) : super(key: key);
#override
_Page_Base createState() => _Page_Base();
}
class _Page_Base extends State<Page_Base> {
Widget build_overlay(BuildContext ctx) {
return Center(
child: Container(
width: double.infinity,
height: double.infinity,
color: Color(0xC09E9E9E),
child: Center(
child: Text(AppState.of(ctx).loading_message),
),
),
);
}
#override
Widget build(BuildContext ctx) {
return Stack(
children: [
AppState.of(ctx).is_logged ? Page_Home() : Page_Login(),
AppState.of(ctx).should_overlay ? build_overlay(ctx) : Material(),
],
);
}
void rebuild() {
// setState() is protected and can not be called
// from outside of the this. scope
setState(() => null);
}
}
Using AppState is the best part, just because the widget does not have to call more than 1 function and it will rebuild with the correct data on complition
class Page_Login extends StatelessWidget {
const Page_Login({Key? key}) : super(key: key);
#override
Widget build(BuildContext ctx) {
return Center(
child: InkWell(
onTap: () => AppState.of(ctx).triger_login('Login'),
child: Container(
width: 200,
height: 200,
color: Colors.greenAccent,
child: Text('Page_Login'),
),
),
);
}
}
class Page_Home extends StatelessWidget {
const Page_Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext ctx) {
return Center(
child: InkWell(
onTap: () => AppState.of(ctx).triger_logout('Logout'),
child: Container(
width: 200,
height: 200,
color: Colors.blueAccent,
child: Text('Page_Home'),
),
),
);
}
}
Global loading indicator
If you want a centralized loading indicator to use in your whole app you could take advantage of Overlay's, which flutter already uses for dialogs, popups, bottom sheets etc. This way we don't introduce new widget in the widget tree.
If you only want to toggle between loading states you can use a StateProvider to handle the simple boolean value, else you could create a State/Change Notifier. This way you decouple your loading state from your AppState
final loadingProvider = StateProvider<bool>((ref) => false);
void main() => runApp(const ProviderScope(child: MaterialApp(home: GlobalLoadingIndicator(child: Home()))));
// This widget should wrap your entire app, but be below MaterialApp in order to have access to the Overlay
class GlobalLoadingIndicator extends ConsumerStatefulWidget {
final Widget child;
const GlobalLoadingIndicator({required this.child, Key? key}) : super(key: key);
#override
ConsumerState createState() => _GlobalLoadingIndicatorState();
}
class _GlobalLoadingIndicatorState extends ConsumerState<GlobalLoadingIndicator> {
//We need to cache the overlay entries we are showing as part of the indicator in order to remove them when the indicator is hidden.
final List<OverlayEntry> _entries = [];
#override
Widget build(BuildContext context) {
ref.listen<bool>(loadingProvider, (previous, next) {
// We just want to make changes if the states are different
if (previous == next) return;
if (next) {
// Add a modal barrier so the user cannot interact with the app while the loading indicator is visible
_entries.add(OverlayEntry(builder: (_) => ModalBarrier(color: Colors.black12.withOpacity(.5))));
_entries.add(OverlayEntry(
builder: (_) =>const Center(
child: Card(child: Padding(padding: EdgeInsets.all(16.0), child: CircularProgressIndicator())))));
// Insert the overlay entries into the overlay to actually show the loading indicator
Overlay.of(context)?.insertAll(_entries);
} else {
// Remove the overlay entries from the overlay to hide the loading indicator
_entries.forEach((e) => e.remove());
// Remove the cached overlay entries from the widget state
_entries.clear();
}
});
return widget.child;
}
}
We insert the GlobalLoadingIndicator high up in the widget tree although anywhere below the MaterialApp is fine (as long as it can access the Overlay via context).
The GlobalLoadingIndicator wont create extra widgets in the widget tree, and will only manage the overlays, here I add two overlays, one is a ModalBarrier which the user from interacting with widgets behind itself. And the other the actual LoadingIndicator. You are free to not add the ModalBarrier, or make it dismissible (or even if you decide to create a more complex loadingProvider, customize it in case you need to cater different use cases).
A sample usage after you have this set up is just switching the state of the loadingProvider, most of the times you would do this programatically, but for interactiveness I'll use a Switch :
class Home extends ConsumerWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, ref) {
final isLoading = ref.watch(loadingProvider);
return Scaffold(
appBar: AppBar(),
body: Center(
child: SwitchListTile(
value: isLoading,
onChanged: (value) {
ref.read(loadingProvider.notifier).state = value;
Future.delayed(const Duration(seconds: 4)).then((value) {
ref.read(loadingProvider.notifier).state = false;
});
},
title: const FlutterLogo(),
),
));
}
}
You can fiddle with this snippet in dartpad
Result:
Per Screen/Section loading indicator
As a side note when displaying loading states inside components of the app I recommend you to use an AnimatedSwitcher , as it fades between the widgets , super handy when dealing with screens which can change content abruptly.
final loadingProvider = StateProvider<bool>((ref) => false);
void main() => runApp(ProviderScope(child: MaterialApp(home: Home())));
class Home extends ConsumerWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, ref) {
final isLoading = ref.watch(loadingProvider);
return Scaffold(
appBar: AppBar(),
body: Center(
child: SwitchListTile(
value: isLoading,
onChanged: (value) {
ref.read(loadingProvider.notifier).state = value;
},
title: AnimatedSwitcher(
duration: Duration(milliseconds: 400),
child: isLoading?CircularProgressIndicator():FlutterLogo()
),
),
));
}
}

Flutter change state from related widget class

Lets assume a class "SpecialButton" and its State-Class "SpecialButtonState"
class SpecialButton extends StatefulWidget {
bool active = false;
SpecialButton({Key key}) : super(key: key);
#override
SpecialButtonState createState() => SpecialButtonState();
}
class SpecialButtonState extends State<SpecialButton> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
decoration:
BoxDecoration(color: this.widget.active ? COLOR_1 : COLOR_2),
child: null);
}
}
In the parent widget, I manage a couple of these buttons. Therefore, I want to assign a state to them. The solution I tried was to introduce a flag "active" in the SpecialButton class which I can easily set to either true or false from the parent widget. I can then use this in the build function of the state class to colorize the button. Unfortunately, this does not work completely as it does not update the button immediately (it needs some kind of state update e.g. by hovering over the element).
My second idea was to introduce this flag as a propper state of the SpecialButtonState class
class SpecialButton extends StatefulWidget {
SpecialButton({Key key}) : super(key: key);
#override
SpecialButtonState createState() => SpecialButtonState();
}
class SpecialButtonState extends State<SpecialButton> {
bool active;
#override
void initState() {
super.initState();
this.active = false;
}
activate() {
this.setState(() {
active = true;
});
}
deactivate() {
this.setState(() {
active = false;
});
}
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(color: this.active ? COLOR_1 : COLOR_2),
child: null);
}
}
As far as I understood, this would be the correct way to work with flutter but it seems that I can't access the functions "activate" or "deactivate" from either the SpecialButton Class or the Parent Class containing the widget.
So my question is: How can I (directly or indirectly through functions) modify a State from the corresponding StatefulWidget Class or the Parent Widget containing it?
There are already some similar questions about this on here on Stack Overflow where I could find hints both to use or not to use global keys for such behavior which i found misleading. Also, due to the rapid ongoing development of flutter, they are probably outdated so I ask this (similar) question again in relation to this exact use case.
EDIT: I forgot to mention that it is crucial that this flag will be changed after creation therefore It will be changed multiple times during its livetime. This requires the widget to redraw.
It is not neсessary to use stateful widget for SpecialButton is you case. You can handle active flag with stateless widget and keys. Example code:
class SomeParent extends StatefulWidget {
const SomeParent({Key key}) : super(key: key);
#override
State<SomeParent> createState() => SomeParentState();
}
class SomeParentState extends State<SomeParent> {
bool _button1IsActive = false;
bool _button2IsActive = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
SpecialButton(
key: UniqueKey(),
active: _button1IsActive,
),
SizedBox(height: 8),
SpecialButton(
key: UniqueKey(),
active: _button2IsActive,
),
SizedBox(height: 16),
TextButton(
child: Text('Toggle button 1'),
onPressed: () {
setState(() {
_button1IsActive = !_button1IsActive;
});
},
),
SizedBox(height: 8),
TextButton(
child: Text('Toggle button 2'),
onPressed: () {
setState(() {
_button2IsActive = !_button2IsActive;
});
},
),
],
),
);
}
}
class SpecialButton extends StatelessWidget {
final bool active;
const SpecialButton({Key key, this.active = false}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: 40,
width: 40,
decoration: BoxDecoration(color: active ? Colors.red : Colors.blue),
);
}
}
SomeParent is my fantasy, just for example. Don't know what your parent is.
Keys are significant here. They tell widget tree when specific widgets with the same type (such as SpecialButton) should be rebuild.
Please try this approach, it should work.
As nvoigt says, your buttons could even be stateless widget , but their parent should be statefull and you should provide them with the corresponding value. e.g.:
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
#override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
bool isEnabled = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
StateLessButton1(isEnabled: isEnabled),
StateLessButton1(isEnabled: !isEnabled),
FloatingActionButton(onPressed: (){
setState(() {
isEnabled = !isEnabled;
});
})
],
);
}
}
Now it just depends on when you want to change that value. If you want to change it inside your buttons, I would recommend you to use a class with ChangeNotifier and a function inside it that changes the value. Otherwise I would recommend not to separate your tree into multiple files

Adding data to a list on another page Flutter

I have a list of event name in a stateful widget like this
main.dart
class Fav extends StatefulWidget {
#override
_FavState createState() => _FavState();
}
class _FavState extends State<Fav> {
final PageController ctrl = PageController(viewportFraction: 0.8);
final Firestore db = Firestore.instance;
Stream slides;
var fav = ['3-Tech Event'];
.
.
.
And on another page, I want to add a string, let's say,
'5-Art Exhibit'
into the
var fav = ['3-Tech Event'];
to get the final result
fav = ['3-Tech Event', '5-Art Exhibit'];
on the page above. How do I do that? Here's my code for the button
Event.dart
class Star extends StatefulWidget {
#override
_StarState createState() => _StarState();
}
class _StarState extends State<Star> {
Color _iconColor = Colors.grey;
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(55.0),
child: Transform.scale(
scale: 2.0,
child: IconButton(
icon: Icon(
Icons.star,
color: _iconColor,
),
onPressed: () {
setState(() {
_iconColor = (_iconColor == Colors.yellow) ? Colors.grey : Colors.yellow;
});
})
),
);
}
}
Thank you in advance!
UPDATE
I followed #Viren V Varasadiya advice and updated my code to this
main.dart
class Fav extends StatefulWidget {
#override
_FavState createState() => _FavState();
}
class _FavState extends State<Fav> {
var fav = ['3-Tech Event'];
updatedata(String item) {
setState(() {
fav.add(item);
});
}
And on the other file, I removed Star class (because it's intended to be used in another class anyway) and it looked like this
class Event extends StatefulWidget {
final eventInfo;
Event({Key key, List eventInfo}) //I have to pass a list of data to this
: this.eventInfo = eventInfo, //page from another class
super(key: key);
final Function updatedata;
Event.addToFavWith({this.updatedata});
#override
_EventState createState() => _EventState();
}
class _EventState extends State<Event> {
Color _iconColor = Colors.grey;
#override
Widget build(BuildContext context) {
.
.
.
Container( //This used to be Container(child:Star())
child: InkWell(
child: Container(
padding: EdgeInsets.all(55.0),
child: Transform.scale(
scale: 2.0,
child: IconButton(
icon: Icon(
Icons.star,
color: _iconColor,
),
onPressed: () {
setState(() {
_iconColor = (_iconColor == Colors.yellow)
? Colors.grey
: Colors.yellow;
widget.updatedata(name);
});
})),
),
)),
And now I get a couple of errors.
All final variables must be initialized, but 'eventInfo' is not. Try
adding an initializer for the field.
All final variables must be initialized, but 'updatedata' is not. Try
adding an initializer for the field.
You have to create a function in parent widget and pass it to child widget and call it in child widget, we work for you.
Following minimal code help you more.
class DeleteWidget extends StatefulWidget {
#override
_DeleteWidgetState createState() => _DeleteWidgetState();
}
class _DeleteWidgetState extends State<DeleteWidget> {
var fav = ['3-Tech Event'];
updatedata(String item) {
setState(() {
fav.add(item);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(fav.toString()),
Star(
updatedata: updatedata,
),
],
)));
}
}
class Star extends StatefulWidget {
final Function updatedata;
Star({this.updatedata});
#override
_StarState createState() => _StarState();
}
class _StarState extends State<Star> {
#override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(
child: Text("add item"),
onPressed: () {
widget.updatedata('5-Art Exhibit');
}),
);
}
}
Update:
There is no need to create named constructor.
class Event extends StatefulWidget {
final eventInfo;
Event({Key key, List eventInfo, this.updatedata}) //I have to pass a list of data to this
: this.eventInfo = eventInfo, //page from another class
super(key: key);
final Function updatedata;
#override
_EventState createState() => _EventState();
}

How to build another Widget inside a button's onTap function

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;
}
}

Calling method from State class

Given a stateful widget, is somehow possible to call a method defined in the State class (the one which extends State<NameOfTheWidget>). Actually, I just want to rebuild the _State class, like calling setState() but from outside of the class. I know how to it from children to parents but not viceversa.
class Foo extends StatefulWidget{
State createState() => new _State();
//...bar() ??
}
class _State extends State<Foo>{
#override
Widget build(BuildContext context) {...}
void bar(){...}
}
EDIT: some real code
First, we hace the equivalent to the inner widget; it's a a customized text field. The point is that I want enable and disable it according to the boolean _activo variable.
import 'package:flutter/material.dart';
import 'package:bukit/widgets/ensure.dart';
class EntradaDatos extends StatelessWidget{
final String _titulo;
final String _hint;
TextEditingController _tec;
FocusNode _fn = new FocusNode();
final String Function(String s) _validador;
final TextInputType _tit;
bool _activo;
/*
* CONSTRUCTOR
*/
EntradaDatos(this._titulo, this._hint, this._validador, this._tit, this._activo){
_tec = new TextEditingController();
}
#override
Widget build(BuildContext context){
print('Construyendo');
return new EnsureVisibleWhenFocused(
focusNode: _fn,
child: new TextFormField(
enabled: _activo,
keyboardType: _tit,
validator: _validador,
autovalidate: true,
focusNode: _fn,
controller: _tec,
decoration: InputDecoration(
labelText: _titulo,
hintText: _hint
),
)
);
}
String getContenido(){
return _tec.text;
}
}
Then I have a concrete implementation of the previous text field, which just extends it:
import 'package:flutter/material.dart';
import 'package:bukit/widgets/entrada_datos.dart';
class EntradaMail extends EntradaDatos{
static String _hint = "nombre#dominio.es";
static String _validador(String s){
if(s.isEmpty){
return 'El campo es obligatorio';
}else{
if(!s.contains('#') || !s.contains('.') || s.contains(' ')){
return 'Introduce una dirección válida';
}else{
String nombre = s.substring(0, s.indexOf('#'));
String servidor = s.substring(s.indexOf('#')+1, s.lastIndexOf('.'));
String dominio = s.substring(s.lastIndexOf('.')+1);
if(nombre.length < 2 || servidor.length < 2 || dominio.length < 2){
return 'Introduce una dirección válida';
}
}
}
}
EntradaMail(String titulo, bool activo) : super(titulo, _hint, _validador, TextInputType.emailAddress, activo);
}
Finally, the equivalent of my outter widget. It's just a checkbox followed by the prevoius EntradaEmail widget. As far as I know, once the checkbox is pressed and the onChange call is made, the setState call should rebuild everything, but I've contrasted with debug messaged that the build method of the first inner widget is never called. My point is enabling and disabling the text field according to the checkbox.
class CampoEnvio extends StatefulWidget{
EntradaMail _mail;
EntradaMovil _movil;
String _tituloMail;
String _tituloMovil;
bool _usaMail = false;
bool _usaMovil = false;
CampoEnvio(this._tituloMail, this._tituloMovil){
_mail = new EntradaMail(_tituloMail, _usaMail);
_movil = new EntradaMovil(_tituloMovil, _usaMovil);
}
State createState() => _State(_mail, _movil, _usaMail, _usaMovil, _tituloMail, _tituloMovil);
}
class _State extends State<CampoEnvio>{
bool _usaMail;
bool _usaMovil;
String _tituloMail;
String _tituloMovil;
EntradaMail _mail;
EntradaMovil _movil;
_State(this._mail, this._movil, this._usaMail, this._usaMovil, this._tituloMail, this._tituloMovil);
#override
Widget build(BuildContext context){
return new Column(
children: <Widget>[
new ListTile(
leading: new SizedBox(
width: 70.0,
child: new Row(
children: <Widget>[
new Checkbox(
value: _usaMail,
activeColor: Colors.black,
onChanged: (value) {
setState(() {
_usaMail = value;
});
},
),
],
),
),
title: _mail,
),
//...
new Divider()
],
);
}
}
Yes, in theory it is possible using a GlobalKey, but not recommended!
class OuterWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => OuterWidgetState();
}
class OuterWidgetState extends State<OuterWidget> {
final _innerKey = GlobalKey<InnerWidgetState>();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
InnerWidget(key: _innerKey),
RaisedButton(
child: Text('call foo'),
onPressed: () {
_innerKey.currentState.foo();
},
)
],
);
}
}
class InnerWidget extends StatefulWidget {
InnerWidget({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => InnerWidgetState();
}
class InnerWidgetState extends State<InnerWidget> {
String _value = 'not foo';
#override
Widget build(BuildContext context) {
return Text(_value);
}
void foo() {
setState(() {
_value = 'totally foo';
});
}
}
Better approach: Instead, what it would be a good idea to pull the state up:
class OuterWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => OuterWidgetState();
}
class OuterWidgetState extends State<OuterWidget> {
String _innerValue;
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
InnerWidget(value: _innerValue),
RaisedButton(
child: Text('call foo'),
onPressed: () {
setState(() {
_innerValue = 'totally foo';
});
},
)
],
);
}
}
class InnerWidget extends StatefulWidget {
InnerWidget({Key key, this.value}) : super(key: key);
final String value;
#override
State<StatefulWidget> createState() => InnerWidgetState();
}
class InnerWidgetState extends State<InnerWidget> {
#override
Widget build(BuildContext context) {
return Text(widget.value);
}
}
If you can, make the inner widget stateless:
class InnerWidget extends StatelessWidget {
InnerWidget({Key key, this.value}) : super(key: key);
final String value;
#override
Widget build(BuildContext context) {
return Text(value);
}
}
If your child is interactive (taps, checkbox...), you can define callbacks with VoidCallback or ValueChanged<T> (or your own typedef) to process the events in the parent widget.
Ok, now that you added the sample code, I will try to explain why your widget does not work, and I will try to explain what other improvements can be made.
First of all, you can improve the readability of your code by using named constructors for all of your widgets, like in my other answers (You can auto-generate them with Android Studio: Define some final fields, then press the lightbulb button to generate the constructor).
The next problem is that widgets which create a TextEditingController must always be stateful widgets! Otherwise the input made by the user will disappear after every build!
Usually you would pass in the TextEditingController from a parent widget (the widget that handles processes data when you submit it)
Also, it is discouraged to extend widgets. Instead, use composition, e.g.:
class EntradaMail extends StatelessWidget {
final String titulo;
// ...
Widget build(BuildContext context) {
return EntradaDatos(
titulo: titulo,
//...
)
}
}
Widget properties should always be public and final (never start with a _).
You are doing some strange things in CampoEnvio.
First of all, you are for some reason passing in all the properties of the widget to the State in createState. That has some consequences which you probably don't intend.
In general it is extremely rare that your State class has constructor parameters, and usually you would not pass properties from the stateful widget to the state.
The problem is that createState is only called once, it is not called again when you call initState in a parent widget. The state is kept until the widget is disposed.
That means your state constructor is only called once as well, and the fields in _State (of CampoEnvio) will stay the same all the time. Even when the parent is rebuilt and calls the constructor of CampoEnvio again, the old values in _State will not be replaced.
It's also very stange that you are creating widgets (EntradaMail and EntradaMovil) in the StatefulWidget.
The class that extends StatefulWidget should not do that! It is basically just a "bag" of properties.
Here is the complete fixed sample code, following the conventions explained above:
class EntradaDatos extends StatefulWidget {
EntradaDatos({Key key, this.titulo, this.hint, this.validador, this.tit, this.activo}) : super(key: key);
final String titulo;
final String hint;
final String Function(String s) validador;
final TextInputType tit;
final bool activo;
#override
State<StatefulWidget> createState() => _EntradaDatosState();
}
class _EntradaDatosState extends State<EntradaDatos> {
// FocusNode and TextEditingController must be the same for the whole lifetime of the widget
// => put into State
TextEditingController _tec;
FocusNode _fn;
#override
void initState() {
super.initState();
_tec = new TextEditingController();
_fn = new FocusNode();
}
#override
Widget build(BuildContext context) {
print('Construyendo');
return new EnsureVisibleWhenFocused(
focusNode: _fn,
child: new TextFormField(
enabled: widget.activo,
keyboardType: widget.tit,
validator: widget.validador,
autovalidate: true,
focusNode: _fn,
controller: _tec,
decoration: InputDecoration(labelText: widget.titulo, hintText: widget.hint),
));
}
String getContenido() {
return _tec.text;
}
}
class EntradaMail extends StatelessWidget {
static String _hint = "nombre#dominio.es";
static String _validador(String s) {
if (s.isEmpty) {
return 'El campo es obligatorio';
} else {
if (!s.contains('#') || !s.contains('.') || s.contains(' ')) {
return 'Introduce una dirección válida';
} else {
String nombre = s.substring(0, s.indexOf('#'));
String servidor = s.substring(s.indexOf('#') + 1, s.lastIndexOf('.'));
String dominio = s.substring(s.lastIndexOf('.') + 1);
if (nombre.length < 2 || servidor.length < 2 || dominio.length < 2) {
return 'Introduce una dirección válida';
}
}
}
}
EntradaMail({Key key, this.titulo, this.activo}) : super(key: key);
final String titulo;
final bool activo;
#override
Widget build(BuildContext context) {
// use composition instead of inheritance
return EntradaDatos(
titulo: titulo,
activo: activo,
validador: _validador,
hint: _hint,
tit: TextInputType.emailAddress,
);
}
}
class CampoEnvio extends StatefulWidget {
const CampoEnvio({Key key, this.tituloMail, this.tituloMovil}) : super(key: key);
final String tituloMail;
final String tituloMovil;
#override
State<StatefulWidget> createState() => new _CampoEnvioState();
}
class _CampoEnvioState extends State<CampoEnvio> {
// I guess these variables are modified here using setState
bool _usaMail;
bool _usaMovil;
#override
Widget build(BuildContext context) {
// just rebuild the widgets whenever build is called!
final mail = new EntradaMail(
titulo: widget.tituloMail,
activo: _usaMail,
);
final movil = new EntradaMovil(
titulo: widget.tituloMovil,
activo: _usaMovil,
);
return new Column(
children: <Widget>[
new ListTile(
leading: new SizedBox(
width: 70.0,
child: new Row(
children: <Widget>[
new Checkbox(
value: _usaMail,
activeColor: Colors.black,
onChanged: (value) {
setState(() {
_usaMail = value;
});
},
),
],
),
),
title: mail,
),
//...
new Divider()
],
);
}
}
It always helps to look at the official samples in the Flutter repositories!