Flutter: calling child class function from parent class of onother file - class

Question:
How to call methodA() from onPressed() of IconButton.
I've tryed to do this by using GlobalKey:
GlobalKey<_MyButtonState> globalKey = GlobalKey();
But it's returns an error.
I have read many forums on this and I have tried all the solutions posed but none of them are working for me.
CODE:
main.dart
import 'package:flutter/material.dart';
import 'button.dart';
void main() {
runApp(MaterialApp( title: 'My app', home: MyApp(),));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.help),
onPressed: () {
// how can I call methodA from here?
},
),
),
body: HomePage(),
),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Center(
child: MyButton(),
);
}
}
button.dart
import 'package:flutter/material.dart';
class MyButton extends StatefulWidget {
#override
_MyButtonState createState() => _MyButtonState();
}
class _MyButtonState extends State<MyButton> {
#override
Widget build(BuildContext context) {
return Container( );
}
void methodA(){
print('methodA');
}
}
I have read many forums on this and I have tried all the solutions posed but none of them are working for me.

first, you will have to import the file as a package in main.dart:
Main.dart: (just writing the way to import the file)
import 'package:prioject_name/file_name.dart';
Note: this is for files under lib directory.
if your file is under a different directory inside lib
then add the path accordingly,
eg: Button.dart is inside the widgets folder inside the lib folder:
lib
|____widgets
|____Button.dart
then the import statement will be as follows:
import 'package:prioject_name/widgets/Button.dart';
Then try your global key method to call the function:
If it is still not working then you can use my method,
how I call methods from different class in onPressed or onTapped:
your Button.dart file.
import 'package:flutter/material.dart';
// changed the method definition class
class MyButton extends StatefulWidget {
void methodA(){
print('methodA');
}
#override
_MyButtonState createState() => _MyButtonState();
}
class _MyButtonState extends State<MyButton> {
#override
Widget build(BuildContext context) {
...
widget.methodA(); // this would call the method A, anywhere inside the Widget build() function.
return Container( );
}
}
Now in Main.dart:
import 'package:prioject_name/Button.dart';
//call the function here using className().functioName();
....
onPressed(){
MyButton().methodA();
}

Take a look at the InheritedWidget class (and watch the videos).
Base class for widgets that efficiently propagate information down the
tree.
You can look at creating an InheritedWidget that contains a ValueNotifier.
class MyInheritedWidget extends InheritedWidget {
final ValueNotifier<int> buttonTapCountNotifier;
const MyInheritedWidget({
Key key,
#required this.buttonTapCountNotifier,
#required Widget child,
}) : assert(child != null),
super(key: key, child: child);
static MyInheritedWidget of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
}
}
Your MyButton class can call MyInheritedWidget.of(context).buttonTapCountNotifier to get hold of the ValueNotifier and add a listener to it.
Each time the ValueNotifier notifies your MyButton class that the value has been incremented, you can execute methodA.

You could use the Provider package which is quite the preferred method to manage state in Flutter apps. This will help you as well in organizing and growing the app in a clever way.
Take a look at the working code below.
define a ChangeNotifier (PressedProvider) which will save
current state of the app in a unique location and the behavior of your onPress function
you wrap your app
with a ChangeNotifierProvider widget
you wrap the receiving
Widget with a Consumer
you get the Provider.of() when you
need to do something and call a method on it
it will notify the Consumer of a change
Code:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(ChangeNotifierProvider<PressedProvider>( // 2
create: (_) => PressedProvider(),
child: MyApp(),
));
}
class PressedProvider extends ChangeNotifier { // 1
void pressButton() {
print("pressButton");
notifyListeners();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
leading: Consumer<PressedProvider>( // 3
builder: (_, provider, widget) => IconButton(
icon: Icon(Icons.help),
onPressed: () {
provider.pressButton();
},
),
),
),
body: Center(
child: MyButton(),
),
),
);
}
}
class MyButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
PressedProvider provider = Provider.of<PressedProvider>(context); // 4
return Center(
child: RawMaterialButton(
child: Text("Press me"),
onPressed: () => provider.pressButton()),
);
}
}

Related

Flutter - how to call child widget's method from parent

Let's say we have two stateful widgets.
ParentWidget(){
}
ChildWidget() {
someMethod(){
// some code, for example setState code
}
}
Now when I use the ChildWidget in ParentWidget, how do I call the someMethod()?
If you need call function on widget you can use:
context.findAncestorWidgetOfExactType<T>()
If you need call function on state of that widget you can use:
context.findRootAncestorStateOfType<T>();
read more at:
https://api.flutter.dev/flutter/widgets/BuildContext/findAncestorWidgetOfExactType.html
https://api.flutter.dev/flutter/widgets/BuildContext/findRootAncestorStateOfType.html
Here is way what I've used.
Make a GlobalKey instance
Pass the Globalkey as a Key parameter to child widget.
Call GlobalKey.currentState.method();
ParentWidget(){
GlobalKey<ChildWidgetState> globalKey = GlobalKey();
ChildWidget(key: globalKey);
...
globalKey.currentState.someMethod();
}
ChildWidget() {
ChildWidget({Key key}) : super(key: key);
someMethod(){
// some code, for example setState code
}
}
TestCode
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
GlobalKey<ChildWidgetState> globalKey = GlobalKey();
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
InkWell(
onTap: () {
globalKey.currentState.someMethod();
},
child: Text('ParentWidget'),
),
ChildWidget(key: globalKey),
],
);
}
}
class ChildWidget extends StatefulWidget {
ChildWidget({Key key}) : super(key: key);
#override
ChildWidgetState createState() => ChildWidgetState();
}
class ChildWidgetState extends State<ChildWidget> {
void someMethod() {
print('someMethod is called');
}
#override
Widget build(BuildContext context) {
return Text('childWidget');
}
}
edit: this approach works, but thanks #Andrija for pointing this out:
Just wanted to note - even though the answers below are good - you
should not be doing this in the first place. This is an anti-pattern
in flutter.

How do I add Widgets to a Column through code

I'm trying to add widgets to a Column dynamically. The following approach does not work as the button does not add the text widgets when clicked. I'd like to understand why this doesn't work and what should I do to make it work. Thanks in advance for the help.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
List<Widget> c = [];
return MaterialApp(
title: 'Flutter Demo',
home: Column(children: [
Column(children: c),
ElevatedButton(onPressed: (){
print("testing");
c.add(Text("testing"));
}, child: Text("Add Text"))
])
);
}
}
Edit: I edited my code to be in a stateful widget and added a setState function around the function that adds the widgets to the container, but it still won't work
import 'package:flutter/material.dart';
class SW extends StatefulWidget {
const SW({ Key? key }) : super(key: key);
#override
_SWState createState() => _SWState();
}
class _SWState extends State<SW> {
#override
Widget build(BuildContext context) {
List<Widget> c = [];
return MaterialApp(
title: 'Flutter Demo',
home: Column(children: [
Column(children: c),
ElevatedButton(onPressed: (){
setState((){
print("testing");
c.add(Text("testing"));
});
},
child: Text("Add Text"))
])
);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SW();
}
}
void main() {
runApp(MyApp());
}
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home:MyApp()));
class MyApp extends StatefulWidget {
#override
_MyState createState() => _MyState();
}
class _MyState extends State<MyApp> {
List<Widget> c = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body:Column(children: [
Column(children: c),
ElevatedButton(onPressed: (){
setState((){
print("testing");
c.add(Text("testing"));
});
}, child: Text("Add Text"))
]),
);
}
}
Wrap your on pressed code in setstate to rebuild the page. The setState function triggers rebuild of the whole widget tree shown in your screen. And without that the text widget will only be added to the list but won't be shown to the screen.

There are some parts that I don't understand well about Flutter's Key

I practiced after watching a video explaining Flutter's Key.
https://api.flutter.dev/flutter/foundation/Key-class.html
This video shows an example of changing the location of a container with a specific color. (About 1 minute and 50 seconds)
In the video, the statefulwidget says that without a key, the location will not change.
But I wrote the example code myself and confirmed that it worked without giving a key to the stateful widget.
I think I wrote the example code wrong. Below is the code I wrote.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: KeyPractice(),
);
}
}
class StatefulColorfulTile extends StatefulWidget {
StatefulColorfulTile({#required this.color});
final Color color;
#override
_StatefulColorfulTileState createState() => _StatefulColorfulTileState();
}
class _StatefulColorfulTileState extends State<StatefulColorfulTile> {
#override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100,
color: widget.color,
);
}
}
class KeyPractice extends StatefulWidget {
#override
_KeyPracticeState createState() => _KeyPracticeState();
}
class _KeyPracticeState extends State<KeyPractice> {
List<Widget> tiles;
#override
void initState() {
super.initState();
tiles = [
StatefulColorfulTile(
color: Colors.blueAccent,
),
StatefulColorfulTile(
color: Colors.amber,
),
];
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: tiles,
),
),
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.autorenew,
),
onPressed: () {
setState(() {
tiles.insert(1, tiles.removeAt(0));
});
},
),
);
}
}
The above codes switch positions with each other.
What happens to the example of how the widget does not reposition each other when the stateful widget in the video does not assign keys?
And I understand that the key works only on the Stateful widget, does the Stateless use the key?
And I understood that Key only works with the Stateful widget. I wonder if the Stateless widget uses a key.
If I misunderstood, please teach me.
You're storing the color in the State of KeyPractice. The example they use stores it in the State of the child, in your case: StatefulColorfulTile.
Below is an example of the use of keys to correctly reposition widgets like you're trying to do. My example ended up very similar to what's shown on this medium article. Removing the keys here prevents the widgets from reflecting the color swap, but the use of the keys allows for the intended behavior.
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: KeyPractice(),
);
}
}
class StatefulColorfulTile extends StatefulWidget {
StatefulColorfulTile({Key key}) : super(key: key);
#override
_StatefulColorfulTileState createState() => _StatefulColorfulTileState();
}
class _StatefulColorfulTileState extends State<StatefulColorfulTile> {
final Color myColor = UniqueColorGenerator.getColor();
#override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100,
color: myColor,
);
}
}
class KeyPractice extends StatefulWidget {
#override
_KeyPracticeState createState() => _KeyPracticeState();
}
class _KeyPracticeState extends State<KeyPractice> {
List<Widget> tiles;
#override
void initState() {
super.initState();
tiles = [
StatefulColorfulTile(key: UniqueKey()),
StatefulColorfulTile(key: UniqueKey()),
];
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: tiles,
),
),
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.autorenew,
),
onPressed: () {
setState(() {
tiles.insert(1, tiles.removeAt(0));
});
},
),
);
}
}
class UniqueColorGenerator {
static Random random = new Random();
static Color getColor() {
return Color.fromARGB(255, random.nextInt(255), random.nextInt(255), random.nextInt(255));
}
}

Flutter updating second parent when button is pressed

I am working on a flutter app and I want to update the second level parent's state when a button is pressed. When the "PressMe" button is pressed, I want MyHomePage's state to have Widget2's title string saved in it's own state. Can someone please help me out with this? The button is a lower level widget and I want to pass the data up two levels. Thanks!
//main.dart
import 'package:flutter/material.dart';
import 'Widget1.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String title2;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Hello"),
),
body: Center(
child: Widget1(),
),
);
}
}
///////////////////////////////
//Widget1.dart
import 'package:flutter/material.dart';
import 'Widget2.dart';
class Widget1 extends StatefulWidget {
_Widget1State createState() => _Widget1State();
}
class _Widget1State extends State<Widget1> {
#override
Widget build(BuildContext context) {
return Widget2();
}
}
///////////////////////////////
//Widget2.dart
import 'package:flutter/material.dart';
class Widget2 extends StatefulWidget {
final String title = "Hello from Widget2";
_Widget2State createState() => _Widget2State();
}
class _Widget2State extends State<Widget2> {
String title = "Hello from Widget2";
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: null,
child: Text(
'PressMe',
style: TextStyle(fontSize: 20)
),
);
}
}
Thanks!
The easiest way to update your parent widget/class from a child is to pass down a function you create in the parent, then call that function from your child when you need to update it. However, that gets messy if you need to pass it down through multiple children. Usually in this case you'll want your parent to be a StatefulWidget and call setState inside the function you create when you assign the new title.
The next solution is to use InheritedWidgets or ChangeNotifiers.
The ideal solution would be to use some form of state management such as Provider or Bloc.

notify child that somewhere in parent onTap happened

could you please show me how can I notify my statefull child widget that somewhere in parent user clicks on button?
I have two separate .dart files
in the first file I described main screen widget with FAB
and in the second one I have ListWidget (like RecyclerView)
If user tap on FAB I want notify my ListWidget about it so it can e.g. add one more item.
I have java/android background but it's quite hard for me to change my mind flow.
The first option would be to build the child widget each time you add an item to the list, passing the list as a parameter to the child.
But using streams is a nice way to avoid rebuilding the child widget each time. I think the following code is a good starting point (You could also use a StreamBuilder to build the list leveraging the stream).
In main.dart
import 'dart:async';
import 'package:base_test_project/expanding_list.dart';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
StreamController<int> _controller = StreamController<int>();
int _number = 0;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new ExpandingList(stream: _controller.stream),
),
floatingActionButton: new FloatingActionButton(
onPressed: () {_controller.add(_number++);},
child: new Icon(Icons.add),
),
);
}
}
In expanding_list.dart
import 'dart:async';
import 'package:flutter/material.dart';
class ExpandingList extends StatefulWidget {
Stream<int> stream;
ExpandingList({this.stream});
#override
_ExpandingListState createState() => _ExpandingListState();
}
class _ExpandingListState extends State<ExpandingList> {
List<int> _myList = [];
#override
void initState() {
super.initState();
widget.stream.listen((number) {
setState(() { _myList.add(number); });
});
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _myList.length,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.all(15.0), child: Text("Item ${_myList[index]}"));
});
}
}