How to use a custom widget based on build-in widgets? - flutter

I want to populate a column inside my scaffold body with my MyContainer widget, but I was not able to access properties of the parent widget in child widget. MyContainer class is working fine but using MyColumn widget does not.
Here is my code:
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Title(
color: const Color(0xFFFFFFFF),
child: const Text("Hello World App")),
),
body: MyColumn(),
),
);
}
}
class MyContainer extends Container {
late int numbr;
MyContainer(numbr) {
this.numbr = numbr;
}
#override
// TODO: implement color
Color? get color => Colors.blue;
#override
// TODO: implement child
Widget? get child => Center(
child: Text("Container $numbr",
style: TextStyle(
fontSize: 34, fontFamily: "Cursive", color: Colors.white)),
);
}
class MyColumn extends Column {
#override
// TODO: implement children
List<Widget> get children {
for (int i = 1; i < 11; i++) {
this.children.add(MyContainer(i));
}
return this.children;
}
}

Use the build-in Column widget and instantiate it like similar widgets: Column(), Text(), etc., instead of extending the class. Please refer to the official dart documentation here, to learn how to properly use the extends key word.
You can´t just add multiple widgets to Scaffold´s body, since it expects a single widget (e.g., a Column, Text or Container).
You can use a custom widget MyCustomColumn to configure your column (see code below), but you can also simply add a column to body and pass the List.generate method to its children property.
Here is a code example:
class MyCustomColumn extends StatelessWidget {
const MyCustomColumn ({ Key? key }) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(children: List.generate(11, (index) {
return MyContainer(i);}
),
);
}
}

Related

Flutter: Why the hashCodes of the following container widgets are changing eveytime I hot-reload the app (save the file)?

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

How to calculate the size of child widget inside customAppbar

I need to calculate the height of a child widget before its parent renders.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Material App',
home: Scaffold(
appBar: CustomAppbar(
widget: Text(
'This is a tex',
),
),
),
);
}
}
The above code just call a Widget CustomAppbar, which receives as parameter a TEXT widget
class CustomAppbar extends StatelessWidget implements PreferredSizeWidget {
final Widget? widget;
const CustomAppbar({
Key? key,
this.widget,
}) : super(key: key);
#override
Size get preferredSize => const Size.fromHeight(130.0);
#override
Widget build(BuildContext context) {
return ClipPath(
clipper: HeaderCustomClipper(),
child: ColoredBox(
color: Color.fromRGBO(255, 191, 14, 1),
child: Column(
children: [
_AppBar(),
if (widget != null) widget as Widget,
],
),
),
);
}
}
The above code just is a customAppbar, I want the height of the child widget (widget) to be captured before the CustomAppbar renders.
If I pass a text, it should be seen like that:
If I don't pass anything, it should be seen like that:
to achieve that you can use GlobalKey -> BuildContext -> RenderBox -> widget-> global position & rendered size.
please check this below link
https://stackoverflow.com/a/49650741/17180860

How to refresh a widget on the outside?

There is a red customView and a button in the page:
I want to change the customView's color to green when I tap the button.
Require:
You must call customView's function changeColor to achieve it;
You can't call page's setState, it's stateless;
Do not use eventBus or provider.
Here is all my code, you can copy and test, input your code in CustomView's changeColor, I desire the easiest way.
import 'package:flutter/material.dart';
class RefreshOutsidePage extends StatelessWidget {
const RefreshOutsidePage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
CustomView customView = CustomView();
return Scaffold(
appBar: AppBar(title: Text('refresh outside')),
body: Column(
children: [
customView,
SizedBox(height: 30),
RaisedButton(
child: Text('refresh outside'),
onPressed: () {
customView.changeColor();
},
),
],
),
);
}
}
class CustomView extends StatefulWidget {
CustomView({Key key}) : super(key: key);
#override
_CustomViewState createState() => _CustomViewState();
void changeColor() {
// input your code here
print('change');
}
}
class _CustomViewState extends State<CustomView> {
Color color = Colors.red;
void changeColor() {
setState(() {
color = Colors.green;
});
}
#override
Widget build(BuildContext context) {
return Container(
width: 200,
height: 200,
color: color,
);
}
}
You can change it through key:
Make CustomViewState public (remove _ at beginning)
Define key and call function changeColor:
class RefreshOutsidePage extends StatelessWidget {
const RefreshOutsidePage({Key key}) : super(key: key);
final _customViewKey = GlobalKey<CustomViewState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('refresh outside')),
body: Column(
children: [
CustomView(key: _customViewKey),
SizedBox(height: 30),
RaisedButton(
child: Text('refresh outside'),
onPressed: () {
_customViewKey.currentState.changeColor();
},
),
],
),
);
}
}
There are two main solutions.
Key solution as mentioned by Autocrab.
State solution where the parent widget becomes Stateful or implement any state management solution in Flutter to update the values of his child.
The child CustomView should be Stateless as it is now because you are not changing the state within widget. So you just require extra parameters received from the parent widget to properly update or get a reference to the widget with the GlobalKey to update it.
If you are using this project for learning or something not legacy I suggest you upgrade Flutter as RaisedButton is deprecated and on the long term you will also have to use null-safety. But that is outside the scope of this question.

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

How to use `GlobalKey` to maintain widgets' states when changing parents?

In Emily Fortuna's article (and video) she mentions:
GlobalKeys have two uses: they allow widgets to change parents
anywhere in your app without losing state, or they can be used to
access information about another widget in a completely different part
of the widget tree. An example of the first scenario might if you
wanted to show the same widget on two different screens, but holding
all the same state, you’d want to use a GlobalKey.
Her article includes a gif demo of an app called "Using GlobalKey to ReuseWidget" but does not provide source code (probably because it's too trivial). You can also see a quick video demo here, starting at 8:30 mark: https://youtu.be/kn0EOS-ZiIc?t=510
How do I implement her demo? Where do I define the GlobalKey variable and how/where do I use it? Basically for example, I want to display a counter that counts up every second, and have it on many different screens. Is that something GlobalKey can help me with?
The most common use-case of using GlobalKey to move a widget around the tree is when conditionally wrapping a "child" into another widget like so:
Widget build(context) {
if (foo) {
return Foo(child: child);
}
return child;
}
With such code, you'll quickly notice that if child is stateful, toggling foo will make child lose its state, which is usually unexpected.
To solve this, we'd make our widget stateful, create a GlobalKey, and wrap child into a KeyedSubtree.
Here's an example:
class Example extends StatefulWidget {
const Example({Key key, this.foo, this.child}) : super(key: key);
final Widget child;
final bool foo;
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
final key = GlobalKey();
#override
Widget build(BuildContext context) {
final child = KeyedSubtree(key: key, child: widget.child);
if (widget.foo) {
return Foo(child: child);
}
return child;
}
}
I would not recommend using GlobalKey for this task.
You should pass the data around, not the widget, not the widget state. For example, if you want a Switch and a Slider like in the demo, you are better off just pass the actual boolean and double behind those two widgets. For more complex data, you should look into Provider, InheritedWidget or alike.
Things have changed since that video was released. Saed's answer (which I rewarded 50 bounty points) might be how it was done in the video, but it no longer works in recent Flutter versions. Basically right now there is no good way to easily implement the demo using GlobalKey.
But...
If you can guarantee that, the two widgets will never be on the screen at the same time, or more precisely, they will never be simultaneously inserted into the widget tree on the same frame, then you could try to use GlobalKey to have the same widget on different parts of the layout.
Note this is a very strict limitation. For example, when swiping to another screen, there is usually a transition animation where both screens are rendered at the same time. That is not okay. So for this demo, I inserted a "blank page" to prevent that when swiping.
How to:
So, if you want the same widget, appearing on very different screens (that hopefully are far from each other), you can use a GlobalKey to do that, with basically 3 lines of code.
First, declare a variable that you can access from both screens:
final _key = GlobalKey();
Then, in your widget, have a constructor that takes in a key and pass it to the parent class:
Foo(key) : super(key: key);
Lastly, whenever you use the widget, pass the same key variable to it:
return Container(
color: Colors.green[100],
child: Foo(_key),
);
Full Source:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatelessWidget {
final _key = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Global Key Demo")),
body: PageView.builder(
itemCount: 3,
itemBuilder: (context, index) {
switch (index) {
case 0:
return Container(
color: Colors.green[100],
child: Foo(_key),
);
break;
case 1:
return Container(
color: Colors.blue[100],
child: Text("Blank Page"),
);
break;
case 2:
return Container(
color: Colors.red[100],
child: Foo(_key),
);
break;
default:
throw "404";
}
},
),
);
}
}
class Foo extends StatefulWidget {
#override
_FooState createState() => _FooState();
Foo(key) : super(key: key);
}
class _FooState extends State<Foo> {
bool _switchValue = false;
double _sliderValue = 0.5;
#override
Widget build(BuildContext context) {
return Column(
children: [
Switch(
value: _switchValue,
onChanged: (v) {
setState(() => _switchValue = v);
},
),
Slider(
value: _sliderValue,
onChanged: (v) {
setState(() => _sliderValue = v);
},
)
],
);
}
}
Update: this was an old approach to tackle the state management and not recommended anymore,please see my comments on this answer and also check user1032613's answer below
Global keys can be used to access the state of a statefull widget from anywhere in the widget tree
import 'package:flutter/material.dart';
main() {
runApp(MaterialApp(
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: App(),
));
}
class App extends StatefulWidget {
#override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
GlobalKey<_CounterState> _counterState;
#override
void initState() {
super.initState();
_counterState = GlobalKey();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
children: <Widget>[
Counter(
key: _counterState,
),
],
)),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.navigate_next),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) {
return Page1(_counterState);
}),
);
},
),
);
}
}
class Counter extends StatefulWidget {
const Counter({
Key key,
}) : super(key: key);
#override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count;
#override
void initState() {
super.initState();
count = 0;
}
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: () {
setState(() {
count++;
});
},
),
Text(count.toString()),
],
);
}
}
class Page1 extends StatefulWidget {
final GlobalKey<_CounterState> counterKey;
Page1( this.counterKey);
#override
_Page1State createState() => _Page1State();
}
class _Page1State extends State<Page1> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Row(
children: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: () {
setState(() {
widget.counterKey.currentState.count++;
print(widget.counterKey.currentState.count);
});
},
),
Text(
widget.counterKey.currentState.count.toString(),
style: TextStyle(fontSize: 50),
),
],
),
),
);
}
}