Flutter: Theme not applied to Text Widget - dart

I am trying to make a custom theme that is applied to only the children of that theme.
However when I run the app, the Text widget that displays "hello" is still blue. I want to make it yellow.
Can anyone show me where i'm going wrong?
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',
theme: ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.lightBlue[800],
accentColor: Colors.cyan[600],
textTheme: TextTheme(body1: TextStyle(color: Colors.blue))),
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> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: Text(widget.title),
),
body: Center(
child: Theme(
data: Theme.of(context).copyWith(
textTheme:
TextTheme(body1: TextStyle(color: Colors.yellow))),
child: Text("hello"))));
}
}

To theme a Text you need to assign the value to style property
Text("Hello", style: Theme.of(context).textTheme.body1)
Make sure to use the correct context when doing Theme.of(context). You need a context that is a child of your new Theme.
You'll need to do the following :
Theme(
child: Builder(
builder: (context) {
return Text("Hello", style: Theme.of(context).textTheme.body1);
}
)
)

This is another way, because sometimes it's more convenient to override the default style
Text widget:
If the style argument is null, the text will use the style from the
closest enclosing DefaultTextStyle.
#override
Widget build(BuildContext context) {
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
TextStyle effectiveTextStyle = style;
if (style == null || style.inherit)
effectiveTextStyle = defaultTextStyle.style.merge(style);
So, if you want to override the default style for the Text widget (when you do not pass the style property), you need to use the DefaultTextStyle widget
return new Scaffold(
appBar: new AppBar(
title: Text(widget.title),
),
body: Center(
child: DefaultTextStyle(
style: Theme.of(context).textTheme.body1.copyWith(color: Colors.yellow),
child: Text("hello"))
)
);
MaterialApp uses its TextStyle as its DefaultTextStyle to encourage developers to be intentional about their DefaultTextStyle

Latest Flutter Release mark body1 has deprecated, so now we can use bodyText2:
Text("My Text", style: Theme.of(context).textTheme.bodyText2)
New params(Right ones are new)
body2 => bodyText1;
body1 => bodyText2;

Related

How to remove null text on the screen in flutter

I build an app in which there are two pages(screens),the first page receives the data from second page.But the problem is that before getting the data from second page it is showing "null" on the first page screen.Below are the codes of these two pages.Note:The first page screen is the main launcher screen.
First Page
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
String value;
MyHomePage({Key key,this.value}):super(key:key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Flutter"),
),
body:Center(
child:new Text("${widget.value}") )
}
Second Page
class _List extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyList(),
);
}
}
class MyList extends StatefulWidget {
#override
_MyListState createState() => _MyListState();
}
class _MyListState extends State<MyList> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: Text("List"),
),
body: new Container(
padding: new EdgeInsets.only(left: 5.0,top: 20.0,right: 5.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
GestureDetector(
onTap: (){
var route=new MaterialPageRoute(builder: (BuildContext context)=>new MyHomePage(value: "Apple",),
);
Navigator.of(context).push(route);
},
child: new Card(
child:
new Column(
children: <Widget>[
new Text('Apple'),
new Text('Banana')
],
),
),
),
);
}
}
You can use a blank Container() widget instead of Text() widget like the code below :
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Flutter"),
),
body:Center(
child: widget.value==null ? Container() : new Text("${widget.value}")
)
);
}
}

How to change the endDrawer icon in flutter?

By default, the endDrawer icon in flutter is the hamburger icon. I wanna change it to a filter icon.
new Scaffold(
endDrawer: Drawer(),
...
}
This should do what you want:
import 'package:flutter/material.dart';
class App extends StatefulWidget {
#override
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
endDrawer: Drawer(),
appBar: AppBar(
actions: [
Builder(
builder: (context) => IconButton(
icon: Icon(Icons.filter),
onPressed: () => Scaffold.of(context).openEndDrawer(),
tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
),
),
],
),
body: new Container(),
),
);
}
}
void main() => runApp(App());
Note the 'Builder' is necessary so that the IconButton gets the context underneath the Scaffold. Without that, it would instead be using the context of the App and therefore wouldn't be able to find the Scaffold.
A different (cleaner?) option would be to make a StatelessWidget that encloses IconButton.

Flutter: Is it possible in flutter to show another dropdownbutton after another dropdownbutton meet a certain requirement

For Example This is the First Dropdownbutton
For Example This is the First Dropdown Sorry i dont have enough Reputation to post the images
Where the Tag will be Select A Region
and Another one will be showing which will be the cities where the cities will be
listed down there depends on the region selected above somewhat like that.
Each time you call setState the build method of your widget will be called and the visual tree gets reconstructed where needed. So, in the onChanged handler for your DropdownButton, save the selection in setState and conditionally add the second DropdownButton. Here's a working example (which may be a little rough around the edges :) ):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
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> {
String _selectedRegion;
String _selectedSecond;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Something before'),
DropdownButton<String>(
value: _selectedRegion,
items: ['Arizona', 'California']
.map((region) => DropdownMenuItem<String>(
child: Text(region), value: region))
.toList(),
onChanged: (newValue) {
setState(() {
_selectedRegion = newValue;
});
},
),
_addSecondDropdown(),
Text('Something after'),
],
),
),
);
}
Widget _addSecondDropdown() {
return _selectedRegion != null
? DropdownButton<String>(
value: _selectedSecond,
items: ['First', 'Second']
.map((region) => DropdownMenuItem<String>(
child: Text(region), value: region))
.toList(),
onChanged: (newValue) {
setState(() {
_selectedSecond = newValue;
});
})
: Container(); // Return an empty Container instead.
}
}
Luke Freeman has a great blog post about Managing visibility in Flutter if you need this in a more extensive/reusable way.

Custom theme not working properly. [Flutter]

I have created the following theme for my app:
ThemeData _buildDarkTheme() {
final baseTheme = ThemeData(fontFamily: "Sunflower",);
return baseTheme.copyWith(
brightness: Brightness.dark,
primaryColor: Colors.grey[800],
accentColor: Colors.grey[850]);
}
I then apply it to my app as follows:
class MyApp extends StatelessWidget {
MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return new MaterialApp(
theme: _buildDarkTheme(),
home: new Scaffold(
appBar: _buildAppBar(),
body: new Container(
color: Theme.of(context).accentColor,
height: double.infinity,
child: new ListView.builder(...
However, when I try to access the accent color inside the container (or anywhere else) instead of it being the expected, Colors.grey[850], it instead defaults to blue. Also, trying to use the custom font Sunflower font family does not work, but when I instead use
new Text("Hello World", style: new TextStyle(fontFamily: "Sunflower"))
The font appears correctly.
I am new to flutter and dart so any help resolving these issues would be appreciated.
This is to do with how context and Theme.of work.
From the Theme class source code:
static ThemeData of(BuildContext context, { bool shadowThemeOnly = false }) {
final _InheritedTheme inheritedTheme =
context.inheritFromWidgetOfExactType(_InheritedTheme);
if (shadowThemeOnly) {
if (inheritedTheme == null || inheritedTheme.theme.isMaterialAppTheme)
return null;
return inheritedTheme.theme.data;
}
final ThemeData colorTheme = (inheritedTheme != null) ? inheritedTheme.theme.data : _kFallbackTheme;
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final TextTheme geometryTheme = localizations?.localTextGeometry ?? MaterialTextGeometry.englishLike;
return ThemeData.localize(colorTheme, geometryTheme);
}
Theme.of (and Navigator.of(), ....of() etc), look at the context you pass them and then iterate upwards through the tree of widgets looking for a widget of the type specified.
Now, looking at your code
Widget build(BuildContext context) {
return new MaterialApp(
theme: _buildDarkTheme(),
home: new Scaffold(
appBar: _buildAppBar(),
body: new Container(
color: Theme.of(context).accentColor,
you can see that the context you're passing into Theme.of is actually the context above the theme you're creating. So it won't find your theme and will revert to the default. This is because the widget tree looks somewhat like the following (ignoring all the intermediate layers, with the arrow pointing to the context you're using.
MyApp - context <--------
MaterialApp
Theme
Scaffold
There are two ways to fix this; the first is to use a Builder class to build your widget within a closure that has the context below the theme. That would look something like this:
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: _buildDarkTheme(),
home: Scaffold(
appBar: _buildAppBar(),
body: Builder(
builder: (context) => Container(
color: Theme.of(context).accentColor,
height: double.infinity,
child: ListView.builder(...)
),
),
),
);
}
}
And it would make a tree that looks somewhat like this:
MyApp - context
MaterialApp
Theme
Scaffold
Builder - context <---------
The other (preferable) option is to split out the code for your builder into its own class - either a StatelessWidget-inherited class or a StatefulWidget and State pair.
I had the same problem using themes in the MaterialApp class. If you want to have only one theme for your entire Flutter app, you just have to use one theme in the main page (the one that calls all the other pages).
After doing that, in the other pages, never return the MaterialApp class, otherwise it will overwrite your theme and use the default one.
Here is the code for my main page:
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
fontFamily: "Test",
primaryColor: Colors.yellow,
buttonColor: Colors.yellow,
),
routes: {
'/': (BuildContext context) => AuthPage(),
},
);
After that, in the AuthPage, return Scaffold and not MaterialApp.
why wouldn't you make a _darkTheme variable like this:
ThemeData _darkTheme = _buildDarkTheme();
and then use it to define to color?
class MyApp extends StatelessWidget {
MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: _buildDarkTheme(),
home: Scaffold(
appBar: _buildAppBar(),
body: Container(
color: _darkTheme.accentColor,
height: double.infinity,
child: ListView.builder(...

setState() rerendering of particular Widget. Need code fix

Hi,
Below code example is taken from when we create a new project in flutter.
Question:
_MyHomePageState will rerender as soon as _incrementCounter is pressed. This will rerender appBar, body, and FloatingActionButton (build method which is costly). Am I right? If right, the only thing that should rerender is a body with text.
How can I separate Widgets that needs re-rendering only? Means separating Stateless and Stateful in the below code. I hope you get my point?
Thanks
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Hot reload'),
);
}
}
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> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'You have pushed the button this many times:',
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
),
);
}
}
Flutter will determine which Widgets need to be recreated or rebuilt, and only recreate / rebuild those.
IIRC, on each setState() (or animation tick, or other re-render), Flutter traverses the Widget tree and compares the before and after state of each Widget tree branch. A stateful widget is only rebuilt if its state has actually changed, and only changed stateless Widgets will be recreated. So in your case, _MyHomePageState is rebuilt, but only the second Text widget is recreated.
See
https://www.youtube.com/watch?v=dkyY9WCGMi0&t=171s
for a far more detailed explanation of Widgets (and underlying elements and render boxes).

Resources