I was referencing to the Flutter Docs, but I am not able to correctly read them. What I am trying to say is that I am not able to correctly understand them. I am having questions like "How would I know if a property accepts Widget or double or MaterialStateProperty?"
I was trying to understand the elevation property under ButtonStyle class under ButtonStyleButton class. It is written as:
elevation → MaterialStateProperty<double?>?
The elevation of the button's Material.
I can't understand this syntax. What will be the input of elevation if I am defining the custom ButtonClassButton class as following:
class RoundIconButton extends StatelessWidget {
const RoundIconButton({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ButtonStyleButton(
onPressed: () {},
style: ButtonStyle(
elevation: ?????????????,
),
);
}
}
The MaterialState enum provides interactive states that some of the Material widgets can take on when receiving input from the user. The MaterialStateProperty<T> class is an interface for classes that resolve to a value of type T based on a widget's interactive "state".
To provide more clarification, please refer the Static Methods under MaterialStateProperty class here.
If you have a single value for all states (say 10.0 as the double value), you can set the elevation as follows:
class RoundIconButton extends StatelessWidget {
const RoundIconButton({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ButtonStyleButton(
onPressed: () {},
style: ButtonStyle(
elevation: MaterialStateProperty.all(10),
),
);
}
}
Related
I'm working on a big app tightly with the designer.
I was a part of the style guide and the component design process,the designer handed off his work and now its my turn to translate some prototypes and styles to real flutter app.
I'm having a really hard time translating the style guide to flutter widgets.
For example, I have a custom chip widget created from scratch, with many style properties.
There are two main different styles for this chip (normal and highlighted), and the chip is used widely (I believe more than 50 references), excluding other more complex widgets using it.
I tried :
Creating a style class for each big widget, and created extension on ThemeData.
class MyCustomChip extends StatelessWidget {
Widget build(BuildContext context) {
final style = Theme.of(context).myCustomChip;
/** return the custom chip **/
}
}
class MyCustomChipStyle {
/** many style properties **/
}
extension MyCustomChipThemeData on ThemeData {
MyCustomChipStyle get myCustomChip => ...
}
Created a static style class, and passed relevant style properties every time the widget used.
class MyCustomChip extends StatelessWidget {
final Color color;
final BorderRadius borderRadius;
/** many more styling properties **/
}
abstract class MyCustomChipValues {
static const BorderRadius kBorder = /** Getting value from a predefined radiuses **/
static const Color kColor = /** Getting value from a predefined palette **/
/** Many more **/
}
class WidgetUsingTheChip extends StatelessWidget {
Widget build(BuildContext context) {
/** those lines will be duplicated alot **/
return MyCustomChip(
color: MyCustomChipValues.kColor,
borderRadius: MyCustomChipValues.kBorder,
/** Many more **/
);
}
}
Used all the relevant style properties inside the custom widget, and didn't expose them.
abstract class MyCustomChipValues {
static const BorderRadius kBorder = /** Getting value from a predefined radiuses **/
static const Color kColor = /** Getting value from a predefined palette **/
/** Many more **/
}
class MyCustomChip extends StatelessWidget {
Widget build(BuildContext context) {
/** making it virtually impossible to change style depending on state **/
return Container(
color: MyCustomChipValues.kColor,
/** Many more **/
);
}
}
class WidgetUsingTheChip extends StatelessWidget {
Widget build(BuildContext context) {
return MyCustomChip();
}
}
I have defined other style classes :
Insets (various paddings and margins)
Colors (our palette, with buttons, chips, input, scaffold, etc..)
BorderRadiuses
Shadows
Animations (duration and curve)
Guidelines (based on the gutter and the design grid we prototyped with)
Other classes...
I cant wrap my head on which of the following is more easy to maintain and refactor in the time of need..
Any suggestion on how to manage this whole Style guide -> code will be helpful,
especially on how to manage custom widget styles.
Thanks !
Basically you need to understand the concept of custom widgets and know the different types of chips that exist.
You can have several types of bullets - choice, filter, action, input - Chips. Chips - Material Design
From the different types of styles that material design offers, you adapt this to the widgets you receive from your design team. For example wrap your bullets in a Column to have an extra Label.
class MyActionChip extends StatelessWidget {
/// Default chip have a string label
const MyActionChip({
required this.label,
required this.onPressed,
Key? key,
this.onTapAvatar,
}) : child = null, super(key: key);
/// Default chip have a widget label
const MyActionChip.child({
required this.child,
required this.onPressed,
Key? key,
this.onTapAvatar,
}) : label = null, super(key: key);
final String? label;
final Widget? child;
final VoidCallback? onTapAvatar;
final Function()? onPressed;
#override
Widget build(BuildContext context) {
return InputChip(
backgroundColor: MyColors.milk,
shape: const RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(MyConstants.borderRadiusMedium)),
side: BorderSide(color: MyColors.anthracite20),
),
avatar: const Icon(
MyIcons.plus,
size: 18,
color: MyColors.anthracite,
),
labelPadding:
const EdgeInsets.only(right: SdConstants.contentPaddingMedium),
label: child ??
Text(
label!,
style: const TextStyle(
color: MyColors.anthracite,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
onPressed: onPressed,
);
}
}
In complex cases, you can build a custom chip according to the complexity.
Note: the parameters that vary according to the screens and callbacks are simply exposed outside the constructor. You need several constructors/classes to customize all your chips.
I would recommend using InheritedWidget, then you don't have to rely on external packages.
For example:
class CustomStyle extends InheritedWidget {
const CustomStyle({
Key? key,
required this.data,
required Widget child,
}) : super(key: key, child: child);
final StyleData data;
static CustomStyle of(BuildContext context) {
final CustomStyle? result =
context.dependOnInheritedWidgetOfExactType<CustomStyle>();
assert(result != null, 'No CustomStyle found in context');
return result!;
}
#override
bool updateShouldNotify(CustomStyle oldWidget) => data != oldWidget.data;
}
You could create these and add to your CustomStyle in whatever way you want, maybe have multiple class depending on the widget, or separate them by colors.
class StyleData {
const StyleData({
required this.color,
required this.padding,
});
final Color color;
final EdgeInsets padding;
}
Wrap your MaterialApp with your CustomStyle, pass your data.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return CustomStyle(
data: const StyleData(
color: Colors.blue,
padding: EdgeInsets.zero,
),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
Then you can access you styling in all your custom widgets:
class CustomContainer extends StatelessWidget {
const CustomContainer({Key? key, this.color, this.padding}) : super(key: key);
final Color? color;
final EdgeInsets? padding;
#override
Widget build(BuildContext context) {
return Container(
color: color ?? CustomStyle.of(context).data.color,
padding: padding ?? CustomStyle.of(context).data.padding,
child: const Text('Hello'),
);
}
}
Any questions just let me know.
Code A:
class MyContainer extends StatelessWidget {
const MyContainer({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
// No class type
final container = Container(
width: 100,
height: 100,
color: Colors.red,
);
return container;
}
}
Code B:
class MyContainer extends StatelessWidget {
const MyContainer({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
// Add class type
final Widget container = Container(
width: 100,
height: 100,
color: Colors.red,
);
return container;
}
}
I prefer Code A, but I saw some flutter source code in AppBar:
final Widget toolbar = NavigationToolbar(
leading: leading,
middle: title,
trailing: actions,
centerMiddle: widget._getEffectiveCenterTitle(theme),
middleSpacing: widget.titleSpacing,
);
It's like Code B, why flutter official add class type? For performance? For more readable? or other reasons?
Should I add class type when I use final with local widget?
Dart is type-safe and you decide to add a type or not. Type annotations are optional, because Dart VM performs runtime checks and to ensure variable’s value always matches the variable’s static type. See this for more on Dart Type System.
From StatefulWidget API:
A widget that has mutable state.
From State API:
The logic and internal state for a StatefulWidget.
My interpretation of state is basically instance fields of an object or properties of an object.
Based on the same APIs, StatefulWidget only has these 3 properties: hashcode, key, runtimeType.
Clearly, StatefulWidget does not have a 'has-a' relationship with State as mentioned by the API. Being very new to Flutter, the only way I know that StatefulWidget accesses State is via the createState() method, this seems to indicate a dependency relationship rather than 'has-a'.
Hence, my question: What is meant to "have a state" since StatefulWidget does not have a 'has-a' relationship with State
Put it simple, State stands for the data that can be changed/ manipulated. For example it can be Colors, Size or Text value of a Widget. Look at this simple widget:
class SampleWidget extends StatefulWidget {
#override
_SampleWidgetState createState() => _SampleWidgetState();
}
class _SampleWidgetState extends State<SampleWidget> {
String label = 'Sample Button';
Color? labelColor = Colors.black;
#override
Widget build(BuildContext context) {
return SizedBox(
width: double.maxFinite,
height: 45,
child: TextButton(
child: Text(label),
style: TextButton.styleFrom(
primary: labelColor,
backgroundColor: Theme.of(context).accentColor,
textStyle: UITextStyle.buttonStyle()),
onPressed: () => setState(() {
labelColor = Colors.red;
label = 'Button Pressed';
}),
),
);
}
}
Here we have 2 states: The content of the button's label and the color of the label. By pressing the button, we change the content and color to different value, thus manipulating the State of this StatefulWidget.
There are 2 general types of State according to the documentation. This is a good start to understand what the State means within the Flutter framework.
Can anyone explain why I am getting this error and how to fix it?
I was using my _page() function to create my homepage, but now that I have created a new file for MyHomePage, it is giving me that type error. If I remove that "as Container" part of my context.read line, I get a different error.
I am a bit confused on the "as Container" part anyway, but I think it has to do with flutter 2.0, as it wasn't there until after I migrated to 2.0.
When looking into this error, I saw that I should try to restart the application, so I did that and it seemed to work, until I switch to a different page, and then try switching back to the home page, which is when I get the error.
I am not sure if this is important for this error, but I am using Riverpod for my state management.
my_drawer.dart:
class MyDrawer extends StatefulWidget {
MyDrawer({Key? key}) : super(key: key);
#override
_MyDrawerState createState() => _MyDrawerState();
}
class _MyDrawerState extends State<MyDrawer> {
Widget _homePage = MyHomePage();
Widget _galleryPage = _page(Colors.white, "Gallery");
Widget _bookAppPage = _page(Colors.white, "Book An Appointment");
Widget _aboutUsPage = _page(Colors.white, "About Us");
Widget _contactUsPage = _page(Colors.white, "Contact Us");
Widget _settingsPage = _page(Colors.white, "Settings");
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) {
context.read(fragmentProvider).state = _homePage as Container;
});
}
...
...
...
// Temp function to create the rest of the pages
_page(Color color, String title) {
return Container(
height: double.infinity,
width: double.infinity,
color: color,
child: Center(
child: Text(
'$title',
style: TextStyle(fontSize: 30, color: Colors.black),
),
),
);
}
Here is MyHomePage class:
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar(),
drawer: MyDrawer(),
body: Stack(
children: [
Text("Home"),
Consumer(
builder: (context, watch, _) {
final body = watch(fragmentProvider).state;
return body;
},
),
],
),
);
}
}
You're trying to cast _homePage as a Container, but _homePage is an instance of MyHomePage which is a StatefulWidget. All your other pages are actually wrapped in Container, so those casts will succeed.
I'm not sure what the type of context.read(fragmentProvider).state is, so it's hard to say what's the actual fix here without more information.
From this code:
context.read(fragmentProvider).state = _homePage as Container;
Replace the Container with Widget:
context.read(fragmentProvider).state = _homePage as Widget;
I have refactored my code in order to get the result that I was looking for. Instead of using Riverpod and trying to use state management to switch between pages, I opted for the built in Navigator and MaterialPageRoute widgets to navigate between pages in my drawer.
I replaced this code for each context.read() line.
Navigator.push(context,
MaterialPageRoute(builder: (context) => new MyHomePage()));
I removed the Consumer widget as well as all of the Widget _...Page = ...; lines. I also got rid of my _page() function and just went ahead and created basic pages for each page and then imported them into my_drawer.dart.
As Alex Hartford was saying in the comments, I was just over complicating everything by trying to use Riverpod to switch between each page.
After making these changes, the app behaves exactly as I need it to. Thanks to everyone who replied and helped me out. I really appreciate it!
Say I want to create a button that every time that it's clicked it changes color.
Its starting color is required in the constructor.
The following code works fine, but when I hover on top of TestButton I get this message: "This class (or a class which this class inherits from) is marked as '#immutable', but one or more of its instance fields are not final: TestButton.color".
If it should be final but I need it to change, what's the solution?
Why does it have be final if it works anyways?
class TestButton extends StatefulWidget {
TestButton({this.color});
Color color;
#override
_TestButtonState createState() => _TestButtonState();
}
class _TestButtonState extends State<TestButton> {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
setState(() {
widget.color = widget.color == Colors.red ? Colors.blue : Colors.red;
});
},
child: Icon(
Icons.add,
size: 80,
),
color: widget.color,
);
}
}
You can have variables in the State object of the StatefulWidget, not in the StatefulWidget itself.
If you need to pass the value from another widget, you can pass it and reassign it to a State variable in the initState function.
Example:
class TestButton extends StatefulWidget {
TestButton({this.passedcolor});
final Color passedColor;
#override
_TestButtonState createState() => _TestButtonState();
}
class _TestButtonState extends State<TestButton> {
Color color;
#override
initState(){
color = widget.passedColor;
super.initState()
}
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
setState(() {
color = color == Colors.red ? Colors.blue : Colors.red;
});
},
child: Icon(
Icons.add,
size: 80,
),
color: color,
);
}
}
and then you can update it using setState to any color you wish.
As to why the value passed in the constructor has to be final or why you can't change it is because the StatefulWidget itself is immutable and holds immutable data, but it also holds a mutable State object which is a store for all the state aka mutable data the widget requires.
Quoting from Flutter docs:
StatefulWidget instances themselves are immutable and store their mutable state either in separate State objects that are created by the createState method, or in objects to which that State subscribes, for example Stream or ChangeNotifier objects, to which references are stored in final fields on the StatefulWidget itself.
You can read more on it here:
StatefulWidget class documentation on Flutter website