I'm trying to create a tile for my listview. While the tile is working when placed within the listview file, I'm wondering how would I go about encapsulating the tile widget into its own class file.
Specifically, if the tile object does not take an argument, I can simply extend a stateless widget and call upon the build method to return a new tile object.
But if the tile object is to be created with arguments (i.e. custom text), how do I pass this information along? Or would it be better to leave the widget in the listview class itself?
Example:
class Tile extends StatelessWidget {
#override
Widget build(BuildContext context){
return _tile(); //Error, How do i pass the arguments?
}
Widget _tile(String text, String time) {
return new Align(
child: new Container(
// padding: EdgeInsets.all(5.0),
...
I think you can simply create a constructor and use it
import 'package:flutter/material.dart';
class Tile extends StatelessWidget {
final String text;
final String time;
/// Here is your constructor
Tile({Key key, this.text, this.time});
#override
Widget build(BuildContext context) {
return _buildTitle(this.text, this.time);
}
Widget _buildTitle(String text, String time) {
return new Align(
child: new Container(
// padding: EdgeInsets.all(5.0),
));
}
}
Generally when creating a widget constructor you also add a Key and call super. Variables should also be marked final since widgets are immutable.
class Tile extends StatelessWidget {
// make these final
final String text;
final String time;
// constructor
const Tile({Key key, this.text, this.time}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListTile(
// ...
);
}
}
And call it like this:
Tile(text: 'hello', time: '5:30');
It is so common to create custom constructors that there is even a shortcut for it in Android Studio.
Write the names of your final variables.
Put your cursor on them and press Option+Retern (or Alt+Enter).
Image source here.
Related
How does the parent component trigger the methods of the child component? How do I trigger the click method of son in fathor? As shown below:
As mentioned, you need to use a callback. What this means it that you pass a function to your child that it calls when it needs to. You'll be working from the inside up, not top down.
You'll store the function as a member variable and when your child is clicked you'll call that function. The function will be called in your parent, you can pass whatever data you want back to the parent just like calling a normal function.
Here's some pseudo-code you can use to make the adjustment
class son extends StatelessWidget {
Function onClicked;
son({this.onClicked});
Widget build(...) {
return GestureDetector(
child: Container(...),
onTap: onClicked
)
}
}
class father extends StatelessWidget {
Widget build(...) {
return Container(
child:son(onClicked: _clicked)
)
}
void _clicked() {
print('clicked');
}
}
Also, Definitely change your class names to start with a Capital letter.
class Father extends StatelessWidget {
var child;
#override
Widget build(BuildContext context) {
child = son();
return Scaffold(
body: child,
floatingActionButton: FloatingActionButton(
onPressed: () => child?.childFunction(),
),
);
}
}
class Son extends StatelessWidget {
void childFunction() => print('called in child widget');
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
);
}
}
The Flutter documentation for InheritedWidget says
Base class for widgets that efficiently propagate information down the tree.
To obtain the nearest instance of a particular type of inherited widget from > a build context, use BuildContext.inheritFromWidgetOfExactType.
Inherited widgets, when referenced in this way, will cause the consumer
to rebuild when the inherited widget itself changes state.
Given that widgets in Flutter are immutable, and in the example code..
class FrogColor extends InheritedWidget {
const FrogColor({
Key key,
#required this.color,
#required Widget child,
}) : assert(color != null),
assert(child != null),
super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
return context.inheritFromWidgetOfExactType(FrogColor);
}
#override
bool updateShouldNotify(FrogColor old) => color != old.color;
}
the color property is final so cannot be reassigned. Assuming this widget is right at the top of the tree, as in most examples, when will this ever be useful. For the widget to be replaced, a new instance will have to be created.
Presumably where this is done, a new instance of whatever is passed as child will be created too, causing that child's descendants to also rebuild, creating new instances of its childresn etc..
Ending up with the whole tree rebuilt anyway. So the selective updating applied by using inheritFromWidgetOfExactType is pointless, when the data of an instance of InheritedWidget will never change for that instance?
Edit:
This is the simplest example of what I don't understand that I can put together.
In this example, the only way to "change" the InheritedWidget/FrogColor which is near the root of the application is to have its parent (MyApp) rebuild. This causes it to rebuild its children and create a new instance of FrogColor and which gets passed a new child instance. I don't see any other way that the InheritedWidget/FrogColor
would change its state as in the documentation
... will cause the consumer to rebuild when the inherited widget itself changes state.
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MyApp());
}
class FrogColor extends InheritedWidget {
const FrogColor({
Key key,
#required this.color,
#required Widget child,
}) : assert(color != null),
assert(child != null),
super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
return context.inheritFromWidgetOfExactType(FrogColor);
}
#override
bool updateShouldNotify(FrogColor old) => color != old.color;
}
class MyApp extends StatefulWidget {
// This widget is the root of your application.
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp>
{
#override
Widget build(BuildContext context) {
var random = Random(DateTime.now().millisecondsSinceEpoch);
return FrogColor(
color : Color.fromARGB(255,random.nextInt(255),random.nextInt(255),random.nextInt(255)),
child:MaterialApp(
title: 'Flutter Demo',
home: Column (
children: <Widget>[
WidgetA(),
Widget1(),
FlatButton(
child:Text("set state",style:TextStyle(color:Colors.white)),
onPressed:() => this.setState((){})
)
]
)
)
);
}
}
class WidgetA extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Ran Build ${this.runtimeType.toString()}");
return WidgetB();
}
}
class WidgetB extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Ran Build ${this.runtimeType.toString()}");
return Text("SomeText",style:TextStyle(color:FrogColor.of(context).color));
}
}
class Widget1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Ran Build ${this.runtimeType.toString()}");
return Widget2();
}
}
class Widget2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Ran Build ${this.runtimeType.toString()}");
return Text("SomeText",style:TextStyle(color:FrogColor.of(context).color));
}
}
Further, the output of this is
I/flutter (24881): Ran Build WidgetA
I/flutter (24881): Ran Build WidgetB
I/flutter (24881): Ran Build Widget1
I/flutter (24881): Ran Build Widget2
So all the child widgets are always rebuilt. Making the registration done in inheritFromWidgetOfExactType pointless also.
Edit2:
In response to #RĂ©miRousselet answer in the comments, modifying the above example, something like
class MyAppState extends State<MyApp>
{
Widget child;
MyAppState()
{
child = MaterialApp(
title: 'Flutter Demo',
home: Column (
children: <Widget>[
WidgetA(),
Widget1(),
FlatButton(
child:Text("set state",style:TextStyle(color:Colors.white)),
onPressed:() => this.setState((){})
)
]
)
);
}
#override
Widget build(BuildContext context) {
var random = Random(DateTime.now().millisecondsSinceEpoch);
return FrogColor(
color : Color.fromARGB(255,random.nextInt(255),random.nextInt(255),random.nextInt(255)),
child: child
);
}
}
works by storing the tree that shouldn't be modified outside of the build function so that the same child tree is passed to the InhertedWidget on each rebuild. This does work only causing the rebuild of the widgets that have registered with inheritFromWidgetOfExactType to get rebuilt, but not the others.
Although #RĂ©miRousselet says it is incorrect to store the subtree as part of the state, I do not believe there is any reason that this is not ok, and infact they do this in some google tutorial videos. Here She has a subtree created and held as part of the state. In her case 2 StatelessColorfulTile() widgets.
Presumably where this is done, a new instance of whatever is passed as a child will be created too, causing that child's descendants to also rebuild, creating new instances of its children etc..
Ending up with the whole tree rebuilt anyway.
That's where your confusion comes from
A widget rebuilding doesn't force its descendants to rebuild.
When a parent rebuild, the framework internally check if newChild == oldChild, in which case the child is not rebuilt.
As such, if the instance of a widget didn't change, or if it overrides
operator== then it is possible for a widget to not rebuild when its parent is updated.
This is also one of the reasons why AnimatedBuilder offer a child property:
AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Container(child: child,);
},
child: Text('Hello world'),
);
This ensures that when for the whole duration of the animation, child is preserved and therefore not rebuilt. Leading to a much more optimized UI.
I want to create an App with Tabs to get the users input. The Problem is, that the different Tabs get different inputs, but i have to collect the inputs for the Database. My idea her was, that the main scaffold collects the inputs from all Tabs and write it in a database! My problem is that I don't know to send data from the tab (statefullWidget in an other file) to the parent class (Scaffold) or run a function from there!
Please help me and sorry for my bad English!
Jonas
You can pass a Function that can be called whenever you want.
Small example
MamaBear class
...
class _MamaBear extends State<MamaBear> {
void hungryBear(String babyBear) {
print("$babyBear is hungry");
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(children: <Widget>[
BabyBear(
"Mark",
(babyBear) {
hungryBear(babyBear);
},
)])));}
BabyBear class
class BabyBear extends StatefulWidget {
final String babyBearName;
final Function onBearAction;
BabyBear(this.babyBearName, this.onBearAction);
#override
_BabyBear createState() => _BabyBear();
}
class _BabyBear extends State<BabyBear> {
#override
Widget build(BuildContext context) {
return Card(
child: RaisedButton(
child: Text("Mama I'm hungry"),
onPressed: () {
widget.onBearAction(widget.babyBearName);
}),
);
}
}
This is a simplified version of the scenario:
class ParentWdiegt extends StatelessWidget{
//
//
floatinActionButton: FloatingActionButtonWidget(onPressed:()=>CustomWidgetState.someMethod(someValue))
//
//somewhere in the ParentWidget tree
child: CustomWidget() //is stateful
}
CustomWidgetState
class CustomWidgetState extends State<CustomWidget>{
//trigger this function when FAB is pressed in parent widget
someMethod(SomeValue) {//}
}
Is there any way that I can expose someMethod in the state object to be triggered when FAB is pressed without using InheritedWidget?
While GlobalKey allows for an easy access to any widget's state ; avoid it.
Widgets should not interact with other widgets directly. This is one of the core principle of Flutter.
Flutter uses reactive programming instead. Where widgets communicate with each others by submitting events. Not by directly editing the desired widget.
The obvious benefit is that widgets stays independant. And potentially dozens of widgets can communicate with each others using the same principle.
I already made an example here on how to make two different widgets share a common editable value.
If you want to call methods instead, this uses the same principle : A Listenable or Stream shared between widgets. But without using AnimatedWidget or StreamBuilder for the listening.
Instead we'll do the listening manually (which requires slighly more boilerplate) to trigger a custom function.
Here's an example using Stream.
import 'dart:async';
import 'package:flutter/material.dart';
class ParentWidget extends StatefulWidget {
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
final changeNotifier = new StreamController.broadcast();
#override
void dispose() {
changeNotifier.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new AnotherWidget(
shouldTriggerChange: changeNotifier.stream,
),
new RaisedButton(
child: new Text("data"),
onPressed: () => changeNotifier.sink.add(null),
)
],
);
}
}
class AnotherWidget extends StatefulWidget {
final Stream shouldTriggerChange;
AnotherWidget({#required this.shouldTriggerChange});
#override
_AnotherWidgetState createState() => _AnotherWidgetState();
}
class _AnotherWidgetState extends State<AnotherWidget> {
StreamSubscription streamSubscription;
#override
initState() {
super.initState();
streamSubscription = widget.shouldTriggerChange.listen((_) => someMethod());
}
#override
didUpdateWidget(AnotherWidget old) {
super.didUpdateWidget(old);
// in case the stream instance changed, subscribe to the new one
if (widget.shouldTriggerChange != old.shouldTriggerChange) {
streamSubscription.cancel();
streamSubscription = widget.shouldTriggerChange.listen((_) => someMethod());
}
}
#override
dispose() {
super.dispose();
streamSubscription.cancel();
}
void someMethod() {
print('Hello World');
}
#override
Widget build(BuildContext context) {
return Container();
}
}
In this example, someMethod of AnotherWidget will be called whenever a click on the RaisedButton instantiated by _ParentWidgetState is performed.
You can use GlobalKey for that:
// some global place
final customWidgetKey = new GlobalKey<CustomWidgetState>();
...
// import the file with "customWidgetKey"
new CustomWidget(key: customWidetKey, ...)
...
// import the file with "customWidgetKey"
floatinActionButton: FloatingActionButtonWidget(
onPressed: ()=>customWidgetKey.currentState.someMethod(someValue))
Here's my code:
import 'package:flutter/material.dart';
void main() {
runApp(new MyStatefulApp(key: App.appStateKey));
}
/// Part [A]. No difference when appStateKey is defined as variable.
class App {
static final GlobalKey<MyAppState> appStateKey = new GlobalKey<MyAppState>();
}
/// Part [B]
class MyStatefulApp extends StatefulWidget {
MyStatefulApp({Key key}) :super(key: key);
#override
MyAppState createState() => new MyAppState();
}
class MyAppState extends State<MyStatefulApp> {
int _counter = 0;
add() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: "App",
theme: new ThemeData(
primarySwatch: _counter % 2 == 0 ? Colors.blue : Colors.red,
),
home: new MyHomePage(),
);
}
}
/// Part [C]
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text("Main"), ),
body: new FlutterLogo(),
floatingActionButton: new FloatingActionButton(
onPressed: () {
App.appStateKey.currentState.add(); // (X)
},
tooltip: "Trigger color change",
child: new Icon(Icons.add),
),
);
}
}
In the code above, when the FAB is clicked, MaterialApp should rebuild, and the primary color will switch between blue and red.
In fact, the code worked, until I attempted to split the portions of the code to different files. App.appStateKey.currentState on line (X) will be become null when:
Part A (The App class, or the variable) is moved to another file;
Part C (MyHomePage and _MyHomePageState) is moved to another file;
Part A and C are moved to another file
So it looks like the GlobalKey.currentState only work when everything involving this GlobalKey is in the same file.
The doc only states that currentState will be null when (1) there is no widget in the tree that matches this global key, (2) that widget is not a StatefulWidget, or the associated State object is not a subtype of T. It doesn't state that everything has to be in the same file.
Breaking classes into files may not be "the Dart way", but I assume it should work anyhow (they're all public). So this puzzles me, and I suspect if I have stumbled upon certain Flutter feature that I am not aware of. Thanks.
That is due to how dart import works.
In dart, there is two way to import sources :
import './relative/path.dart'
import 'myApp/absolute/path.dart'
The thing is, they are not compatible with each others. Both these imports will have a different runtimeType.
But how is that a problem ? I never used relative import
That's a problem, because in some situations you implicitly use "relative imports" : When using a class A defined in foo.dart inside foo.dart.
So, how do I solve the problem ?
There are multiple solutions :
Have everything related to your class App should be inside the same file. (That's the recommended thing in dart)
Extract App into it's own file. And import it everywhere using absolute imports.
Don't use GlobalKey to begin with. As your use case is definitely in the scope of InheritedWidget.