For example I have a Widget in the first.dart file and another widget in the second.dart file. The first widget is a simple Button and the second Widget includes a Container with the color red. How can I change the color of this container when I tap the button?
If both widgets are in one file it would be of course very simple, just call the setState() method. But as I said each widget is on a separate file.
What I've tried? Honestly, nothing much. I'm not sure if this is even possible. My first idea was to use something like ValueListenable but the result was not really great.
First, you need to create a new dart file with a stateless widget, set it to receive arguments with final _color and ColoredContainer(this._color) as shown:
import 'package:flutter/material.dart';
class ColoredContainer extends StatelessWidget {
final _color;
ColoredContainer(this._color);
#override
Widget build(BuildContext context) {
return Container(
width: 100,
height: 100,
color: _color,
);
}
}
Then in the main.dart file, import the dart file and use ColoredContainer as a widget which you can pass the Colors to it, in my case I passed a different Color from a list of colors every time setState is called:
import 'package:flutter/material.dart';
import 'ColoredContainer.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int index = 0;
final List<Color> _colorList = [
Colors.red,
Colors.green,
Colors.blue,
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(
children: <Widget>[
RaisedButton(onPressed: (){
setState(() {
index += 1;
});
}, child: Text('Tap me')),
ColoredContainer(_colorList[index]),
],
),
),
);
}
}
Related
If I run the following application and observe the hashCodes for BuildSizedBoxWidget which I create two instances of, I notice that they are the same even when I hot reload the app. Does this mean that they are the same widget but referenced multiple times? ... But in case of BuildContainerWidget the hashCodes change every time I hot reload the app. Why does this happen?
'''
import 'package:flutter/material.dart';
void main() {
runApp(const MyApps());
}
class MyApps extends StatelessWidget {
const MyApps({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Test',
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
final List<Widget> widgets = const [
BuildSizedBoxWidget(),
BuildSizedBoxWidget(),
BuildContainerWidget(),
BuildContainerWidget()
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('What is happening'),
),
body: Column(
children: widgets,
),
);
}
}
class BuildSizedBoxWidget extends StatelessWidget {
const BuildSizedBoxWidget({super.key});
#override
Widget build(BuildContext context) {
const Widget widget = SizedBox(height: 50, child: Text('test'));
print(widget.hashCode);
return widget;
}
}
class BuildContainerWidget extends StatelessWidget {
const BuildContainerWidget({super.key});
#override
Widget build(BuildContext context) {
Widget widget = Container(height: 50, color: Colors.red);
print(widget.hashCode);
return widget;
}
}
'''
The variables defined in the body of the build method will be re-initialized during SetState.
Variables specified by the const keyword are not initialized.
There are only three parameters required by SizeBox Widget, and all of them can be initialized.
But Container Widget contains many parameters that cannot be initialized. So Container cannot be specified with the const keyword
If you put them outside the body of the build method, the HasCode will not change
class BuildContainerWidget extends StatelessWidget {
BuildContainerWidget({super.key});
Widget widget = Container(key: Key('value'), height: 50, child: Text('test'));
#override
Widget build(BuildContext context) {
print(widget.hashCode);
return widget;
}
}
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()),
);
}
}
I need to change the background color by clicking on the screen to random, I can't figure out how to do it.
import 'package:flutter/widgets.dart';
import 'dart:math';
main() => runApp(
Directionality(
textDirection: TextDirection.ltr,
child: Container(
color: Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0),
child: MyApp(),
),
),
);
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: GestureDetector( // используется как обычный виджет
onTap: () { // одно из свойств GestureDetector
// Этот метод будет вызван, когда дочерний элемент будет нажат
print('You pressed me');
},
),
);
}
}
1. You need to make a StateFulWidget because you want to change the background when tapped.
2. You need to make a list of colors that can be set as the background color.
3. You need to add a variable which holds the current background color's index in the list of colors.
4. You need to change this variable when tapped to set a new background color.
Note: If you want to have random colors you can check the random_color package which is easy to use.
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: "Title"),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
MyHomePageState createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
List<Color> _colors = [Colors.blue, Colors.red, Colors.green, Colors.yellow];
var _index = 0;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: _colors[_index % _colors.length],
body: GestureDetector(
onTap: () {
setState(() {
_index++;
});
},
child: Stack(
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.transparent,
),
],
)
)
);
}
To do this you want to store the color as part of the state of the widget that you want. Then, when you detect a press, you can change the color and call setState to trigger a rebuild to show the new color. This involves modifying your code to use a StatefulWidget as I have done below.
The following code uses the exact same widgets as your original, with just modifications to make the necessary parts stateful.
import 'package:flutter/widgets.dart';
import 'dart:math';
main() => runApp(
Directionality(
textDirection: TextDirection.ltr,
child: MyApp(),
),
);
class MyApp extends StatefulWidget {//Changed to a `StatefulWidget`
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
//Store the color as a part of your `State`
Color color = Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);
#override
Widget build(BuildContext context) {
return Container(
color: color,
child: Container(
child: GestureDetector(
onTap: () {
print('You pressed me');
//`setState` rebuilds your widget to show the new color
//It's not possible to use a `StatelessWidget` here
setState(() {
color = Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);
});
},
),
),
);
}
}
Try this
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Home(),
);
}
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
Color color;
#override
void initState() {
color = Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
color = Color((Random().nextDouble() * 0xFFFFFF).toInt())
.withOpacity(1.0);
});
},
child: Scaffold(
backgroundColor: color,
),
);
}
}
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));
}
}
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]}"));
});
}
}