How do I deal with this insanity of Flutter's closing requirements? - flutter

I'm constantly getting stuck with the ]'s, }'s and )'s at the end of my widgets:
Expanded(
child: TextFormField(
controller: feedbackController,
cursorColor: Colors.white,
style: TextStyle(
color: Colors.white,
),
decoration: InputDecoration(labelText: 'text input'),
)
]),
)
)
]),
I don't know how to deal with this. I find myself wasting so much time just guessing at how to close my triple nested widgets nested within double widgets nested within whatever. It's too convoluted. Is there a finishing tool for building UI's in flutter? An addon that'll close these out for me?
edit: here's what finally closed it.
)
)]))]
)
)]);
}
}

You can format the document to see the widget tree correctly, but basically in your code that you showed above it should be like this:
Expanded(
child: TextFormField(
cursorColor: Colors.white,
style: TextStyle(
color: Colors.white,
),
decoration: InputDecoration(labelText: 'text input'),
)),
Expanded is a class. In the above code you are initializing the class Expanded, and the constructor takes a required child argument of type Widget. Thus you use the class TextFormField.
So basically whenever you use a class you need to use (), each class will have many properties which you seperate them using , (example style is a property which has a value of type TextStyle so you initialize the class and close it ),
You use [] when you have a list example the column widget has a children property which is of type List<Widget>. Finally, you use ; before using }. The semicolon indicates an end of statement, example:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Text("test")
);
}

Related

how can a method return 2 widgets which I would later use in a column

I am trying to create a method that returns a text and a TextInput, instead of using a container I want the widgets to be wrapped easily and sent back, I thought it could be achieved by wrapping the two widgets in ():
Widget customTextFormField(String hintText) {
return(
Align(
alignment: Alignment.centerLeft,
child: Text(
"Password",
style: TextStyle(
fontSize: 16.0
),
),
),
TextFormField(
style: TextStyle(color: Colors.grey),
controller: TextEditingController(text: ""),
autofocus: true,
cursorColor: Colors.blue,
decoration: new InputDecoration(
hintStyle: TextStyle(color: Colors.grey),
contentPadding: EdgeInsets.only(left: 15),
border: new OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.grey, width: 1.0),
borderRadius: BorderRadius.circular(4.0),
),
),
));
}
I want this to be used in a Column widget. Using the above code doesn't work as () doesn't wrap the two widgets as one. I also used
Return a list of Widgets.
List<Widget> customTextFormField() {
return [
Container(),
TextFormField(),
];
}
Use spread operator inside the column
return Column(children: [
...customTextFormField()
]);
You either want them to return in a Column
return Column(children: [
Align(),
TextFormField()
];
or return a List, to be used in a column or listview for example
List<Widget> customTextFormField(String hintText) {
return [
Align(),
TextFormField(),
];
}
Edit: Returning widgets from methods is considered an anti-pattern. Check this out for more info (watch the video "Widgets vs helper methods"): https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html
When trying to create a reusable piece of UI, prefer using a widget
rather than a helper method. For example, if there was a function used
to build a widget, a State.setState call would require Flutter to
entirely rebuild the returned wrapping widget. If a Widget was used
instead, Flutter would be able to efficiently re-render only those
parts that really need to be updated. Even better, if the created
widget is const, Flutter would short-circuit most of the rebuild work.

Flutter - Dynamic suffix text for TextField?

I'm building a simple soundboard app in Flutter, which includes a search bar at the top of the application.
At the point where the search is activated, the interesting bit of the widget tree is as follows:
home: Scaffold(
appBar: AppBar(
title: new TextField(
controller: _filter,
decoration: new InputDecoration(
prefixIcon: new Icon(Icons.search, color: Colors.white),
hintText: 'Search...'
),
As text is typed into the search bar, a listener on the _filter updates a list of quotes which is used to build the body of the app. This is all working fine.
However, I would now like the app to show a count of the returned results, and since this is sitting in my header bar I'd like it to sit in-line with the bar itself, something like:
Things I've tried, all within the InputDecoration:
Setting suffixText - this is styled right and in the right place, however it doesn't update as the _filter changes because I'm not reconstructing the TextField every time (and I can't do this as it messes up what's been typed in).
Setting suffix to a full TextField widget with its own controller. This gets it auto-updating, but for some reason obscures my actual search text. I tried making the background transparent but that hasn't helped - new TextField(controller: _searchCountController, readOnly: true, textAlign: TextAlign.end, style: TextStyle(backgroundColor: Colors.transparent),). In the below screenshot, I've typed a 'w' which has updated the count to 84 (but I can't see the 'w' I've typed...)
Setting counter instead of suffix. I can get this to auto-update and not obscure my search, but it appears under the search bar which makes the whole thing look naff. Doesn't really seem appropriate for something sat in the title bar.
Any suggestions for how I can achieve what I'm after? Very new to Flutter so very possible that I've missed something obvious. Hoping there's a simple solution to this :)
Well, as expected it turned out I was missing something obvious and there was a simple solution. I was having trouble getting a simple suffixText to update because I was ultimately caching a component and therefore not giving Flutter a chance to re-render it.
In case it helps anyone, I had followed this post to implement a search bar. The pattern they use is to store an _appBarTitle widget which only gets changed when search is initiated and cancelled. I moved away from this approach, instead having a simple _searching boolean and allowing flutter to do the rest:
Widget _buildAppBar() {
if (_searching) {
return new TextField(
controller: _filter,
decoration: new InputDecoration(
prefixIcon: new Icon(Icons.search, color: Colors.white),
hintText: 'Search...',
suffixText: '${_filteredQuotes.length}',
),
autofocus: true,
style: TextStyle(color: Colors.white));
} else {
return new Text('Pocket Scat');
}
}
With this approach, I don't need to specify a component for the suffix at all. It also has the advantage of automatically scooping up hintColor from my app's style.
You can copy paste run full code below
You can use Row with Expanded(TextField) and Text()
code snippet
AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: TextField(
controller: _filter,
decoration: InputDecoration(
prefixIcon: Icon(Icons.search, color: Colors.white),
hintText: 'Search...',
)),
),
Text('$_counter'),
],
))
working demo
full code
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',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
TextEditingController _filter = TextEditingController();
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: TextField(
controller: _filter,
decoration: InputDecoration(
prefixIcon: Icon(Icons.search, color: Colors.white),
hintText: 'Search...',
)),
),
Text('$_counter'),
],
)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

How share data between widget in PageView?

I would like to make a 4 step form in a PageView. There are 4 widgets each corresponding to a stage. Each widget has a TextField and a button to go to the next page. On the last widget the button must retrieve the 4 values ​​of the Textfields to be able to save them. How can I do ?
Widget build(BuildContext context) {
super.build(context);
Size _screenSize = MediaQuery.of(context).size;
return Scaffold(
key: _scaffoldKey,
body: PageView(
controller: controller,
scrollDirection: scrollDirection,
pageSnapping: true,
children: <Widget>[
TitleInput(pcontainer: widget.pcontainer, controller: controller),
DescriptionInput(pcontainer: widget.pcontainer, controller: controller),
EstimationPage(pcontainer: widget.pcontainer, controller: controller),
ImageCapture()
],
),
);
}```
//Button to go to next page:
Widget showNextPage(PageController controller, String topicTitle) {
return new Padding(
padding: EdgeInsets.fromLTRB(12.0, 45.0, 12.0, 0.0),
child: SizedBox(
height: 60.0,
width: 70,
child: new RaisedButton(
elevation: 5.0,
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(30.0)),
color: Colors.blue,
child: new Text('Suivant',
style: new TextStyle(fontSize: 20.0, color: Colors.white)),
onPressed: () => controller.nextPage(curve: Curves.ease, duration: Duration(milliseconds: 300)),
),
)
);
}
I would like to suggest you an alternative solution: rather than using a PageView, you could try the Stepper widget.
You can define the steps that indicate a sequential order of events that must happen and, of course, it's very customizable. Here's the basic skeleton of how you'd use this widget:
Stepper(
steps: [
Step(
title: Text("Step number 1"),
content: Text("widgets"),
),
Step(
title: Text("Step number 2"),
content: Text("widgets"),
),
],
)
There is a very good article on medium that shows many tips for this widget.
I think that if you have an ordered series of steps, you should really try this widget as it is easy to use and very intuitive for the user.
This specific requirement can be fulfilled by creating a Data Model object to hold all the required data and passing this object through each widget to update respective data.
For example:
class DataModel {
String fullName;
String aboutInfo;
String contactDetails;
}
and then Pass the Model's instance(_dataModel) as below:
mainWidget calls =>fullNameWidget(_dataModel) // updates only fullName
fullNameWidget calls =>descriptionWidget(_dataModel) // updates only aboutInfo
descriptionWidget calls =>contactWidget(_dataModel) // updates only contactDetails
contactWidget calls =>finalWidget(_dataModel) // uses data collected in the _dataModel
The important thing is that you make sure to pass the same model instance in each widget to update its respective field and then use the updated data from the instance to go ahead and save it in the last step.
Simply use the Static Variable
// Define the Static Variable in Global class you can use any name
class Global{
static String s1,s2,s3,s4;
}
you can Assign the Values by
Global.s1 = "someValue";
you can Access the Value by
print(Global.s1);

Cleanly overriding parts of a theme locally in Flutter

I have a widget which has two TextFields as descendants. I would like to apply the same styling to these TextFields. My understanding is that the right way to do this is to apply a localized theme to my widget tree. The following is my attempt. This is a code snippet from my root widget's build function. Is there not a cleaner way to do this?
final ThemeData _themeData = Theme.of(context);
return Theme( // HACK
data: _themeData.copyWith(
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(),
),
textTheme: _themeData.textTheme.copyWith(
subhead: _themeData.textTheme.subhead.copyWith(
fontSize: 30.0,
),
),
),
child: _buildTheRestOfMyWidgetTree(context),
);
The thing that I am annoyed by is that to override a single property (_themeData.textTheme.subhead.fontSize), I have to explicitly and manually make copies of three intermediate data structures (_themeData, then _themeData.textTheme, then _themeData.textTheme.subhead).
While I can understand the frustration of having to "copy" everything, this is how you should do it.
Data are immutable in Flutter. You cannot mutate them, you are forced to clone them with different properties.
Therefore your assumption is correct: If you want to modify a nested property, you have to clone all of its parents too. Which leads to:
final ThemeData theme = Theme.of(context);
theme.copyWith(
textTheme: theme.textTheme.copyWith(
subhead: theme.textTheme.subhead.copyWith(
fontSize: 30.0,
),
),
);
Again: you cannot avoid it.
It would help if you packaged that part of the code up and made it a widget so that your tree is cleaner. That's how it's done in this example.
class TextFieldOverride extends StatelessWidget {
const TextFieldOverride({this.child});
final Widget child;
#override
Widget build(BuildContext context) {
final themeData = Theme.of(context);
return Theme(
child: child,
data: themeData.copyWith(
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder()),
textTheme: themeData.textTheme.copyWith(
subhead: themeData.textTheme.subhead.copyWith(
fontSize: 30.0))));
}
}
...
TextFieldOverride(
child: TextField(...)
)
Or if there are few places the code will be duplicated, you can just make the changes directly:
...
child: TextField(
style: Theme.of(context).textTheme.subhead.copyWith(fontSize: 30.0),
decoration: InputDecoration(border: OutlineInputBorder(),
...
)
)
Or perhaps the best choice is to create a function that does the above for you.
TextField buildTextField(BuildContext context) => TextField(
style: Theme.of(context).textTheme.subhead.copyWith(fontSize: 30.0),
decoration: InputDecoration(border: OutlineInputBorder(),
...
)
)

Custom style or theme for Expansion Tile Header, Flutter

Is there any way to apply custom theme for ExpansionTile. In my case, I want to have the different background colour for Header and children of the expansion tile but When ever the ExpansionTile is expanded, Headers background color changes to that of children?
To apply a custom Theme to any widget, just wrap it with the Theme() widget.
and then specify your custom theme in the data field of the widget.
Theme(
data: ThemeData(/*specify your custom theme here*/),
child: ExpansionTile() /*or any other widget you want to apply the theme to.*/
)
In your case, to customise the header in ExpansionTile,
when ExpansionTile is closed
the style of header text i.e. title depends on
ThemeData.textTheme.subhead (in flutter 2 it's ThemeData.textTheme.subtitle1)
whereas, the style of the arrow icon depends
on ThemeData.unselectedWidget (in flutter 2 it's ThemeData.unselectedWidgetColor)
when ExpansionTile is open
the color of both the widgets depends on ThemeData.accentColor
In a similar fashion you can customise almost any part of the expansion tile by tweaking the theme. For more details check out this link.
Since flutter is built with flexibility in mind. You can do almost anything you want. Create almost any UI you want.
Following the idea of #user3315892, you can implement your own stateful widget for the ExpansionTile, so that you can control what colours you want the header and children to be when the header is expanded or collapsed.
The following example only changes the text foreground and background colours of the header when the expansion tile is expanded or collapsed, but you can do the same for the children widgets.
class CustomExpansionTile extends StatefulWidget {
#override
State createState() => CustomExpansionTileState();
}
class CustomExpansionTileState extends State<CustomExpansionTile> {
bool isExpanded = false;
#override
Widget build(BuildContext context) {
return ExpansionTile(
title: Container(
child: Text(
"HEADER HERE",
style: TextStyle(
color: isExpanded ? Colors.pink : Colors.teal,
),
),
// Change header (which is a Container widget in this case) background colour here.
color: isExpanded ? Colors.orange : Colors.green,
),
leading: Icon(
Icons.face,
size: 36.0,
),
children: <Widget>[
Text("Child Widget One"),
Text("Child Widget Two"),
],
onExpansionChanged: (bool expanding) => setState(() => this.isExpanded = expanding),
);
}
}
I found another way to do it. I don't know if this is the best way but it's simpler for me.
To change the ExpansionTile color, you can wrap it with a Container and give it a color. This will change the entire color of the ExpansionTile, both collapsed and expanded.
But if you want a different color for the children, you can also wrap them with a Container and set another color there. However, keep in mind that you have to "expand" that Container to occupy all the available space (you can do it by setting width and height parameters properly).
Btw, that doesn't change the arrow color.
Widget expansionTileTest(context) {
return Container(
color: Colors.grey,
child: ExpansionTile(
title: Text(
"Title",
style: TextStyle(color: Colors.black),
),
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height * 0.1,
width: MediaQuery.of(context).size.width,
color: Colors.yellow,
child: Center(child: Text("Hi")),
),
],
),
);
}
Collapsed
Expanded
You can set color of header and then body/children wrap in Container and set color to that container.
I used this code to make the trailing ion color Black.
Theme(
data: Theme.of(context).copyWith(accentColor: Colors.black),
child: ExpansionTile(
backgroundColor: Colors.white,
title: Text(
'Title Exmample',
style: TextStyle(color: Colors.black),
),
leading: CircleAvatar(
backgroundColor: Colors.black.withOpacity(.07),
child: Icon(
Icons.apps_outlined,
color: Theme.of(context).primaryColor,
)),
children: _buildTiles(Theme.of(context).primaryColor, Colors.black.withOpacity(.07), context),
),
);
If you want the ExpansionTile title to remain the same color whether closed or expanded, you can set it's style:
ExpansionTile(
title: Text('HEADER HERE', style: TextStyle(color: Colors.red)),
...
),
Additional styling for ExpansionTile is an open feature request:
Feature request: More styling of ExpansionTile #15427