I'm working with Cupertino widgets, and need to locally override my global CupertinoTheme, and use CupertinoTheme widget for this purpose. My use case is to force some 'dark' theme when displaying text on top of images, but the issue is general.
In the following sample, I try to change the font size for one text style (from 42px to 21px), but it is not applied: the two texts have the same size (second should be 21px high).
It seems that CupertinoTheme.of(context) does not read the overriden style, contrary to the documentation
Descendant widgets can retrieve the current CupertinoThemeData by calling CupertinoTheme.of
Here is a sample (that can be tested on DartPad):
import 'package:flutter/cupertino.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoApp(
debugShowCheckedModeBanner: true,
theme: CupertinoThemeData(
brightness: Brightness.dark,
textTheme: CupertinoTextThemeData(
navLargeTitleTextStyle: TextStyle(fontSize: 42)
)
),
home: Home()
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: Column(
children: [
Text(
'Hello, World #1!',
style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle
),
CupertinoTheme(
data: CupertinoThemeData(
textTheme: CupertinoTextThemeData(
navLargeTitleTextStyle: TextStyle(fontSize: 21)
)
),
child: Text(
'Hello, World #2!',
style:
CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle
),
),
]
)
);
}
}
You’re getting the theme from the wrong context. The context must be a descendant of the CupertinoTheme widget (or rather the element that will be created from it). Try:
CupertinoTheme(
data: ...,
child: Builder(
builder: (context) => ... CupertinoTheme.of(contex)...
)
)
With the content parameter of the build method you can access anything done by ancestors of the build-method’s widget. Whatever you do in the build method has no effect on it.
Widgets are recipes for creating a tree of Elements. The context parameter that you get in build(er) method is (a reduced interface of) the element created for that widget. The Foo.of(context) methods typically search through the ancestor elements of context to find a Foo. (In some cases there is caching, so it isn’t a slow search.) When you create a tree of widgets in a build method, you’re just creating widgets; the elements will be created after that build method competes. Using a Builder widget, like I did above, delays creation of the widgets in Builder’s builder parameter until after an elements have been created for the Builder (and the widgets above it). So that is a way to get around your problem. Another way would be to create a new StatelessWidget with the widgets that are children of CupertinoTheme in your code, because it will similarly delay the creation of those widgets until after the element for that stateless widget (and its parents) is created.
Related
I was writing my own responsive layouts like this:
Instead of writing Normal code
Container(padding: EdgeInsets.all(10)),
Text("abc", style: TextStyle(fontSize: 20)),
I used a function responsiveSize()
Container(padding: EdgeInsets.all(responsiveSize(10, context))),
Text("abc", style: TextStyle(fontSize: responsiveSize(20, context))),
and define the function responsiveSize() as
double responsiveSize(double number, BuildContext context){
if (MediaQuery.of(context).size.width < 450) {
return number;
} else {
return number * 1.5;
}
}
or something like this which I can easily change.
But the problem with this is that I have to wrap every double value in my code with the function responsiveSize(), which is very tedious. I am looking for a way to wrap all my widgets in one root widget, like wrapping my MaterialApp widget inside a ResponsiveSize widget:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ResponsiveSize(
child: MaterialApp(
home: HomePage(),
),
);
}
}
and writing Normal code (as mentioned above, use double values as it is without wrapping them in a responsiveSize() function) and define ResponsiveSize widget in some way which I don't know. This way I can use responsive layout by just one widget and can easily remove the root ResponsiveSize widget if I don't want it later.
However this involves changing all double values in all descendant widgets of the ResponsiveSize widget, and this is something which I don't know how to do.
So, I would like to know how I can change all double values of all descendant widgets of a root widget? How should I define the root widget?
The LayoutBuilder widget might be what you're looking for.
i'm still new in using flutter driver in testing, but as far as i know there are few identifiers that we can use to locate / identify elements, like By Text, By Type, etc
But the problem is, the app that i want to test doesn't have the identifier that i can use to locate them (please correct me if i'm wrong).. the widget code of the app looks like this
Widget _buildNextButton() {
return Align(
alignment: Alignment.bottomRight,
child: Container(
child: IconButton(
icon: Icon(Icons.arrow_forward),
onPressed: () => _controller.nextPage(),
),
),
);
}
where that widget is on a class that extends StatefulWidget.
How can i locate that icon in my test script and click it? can i use something like this? And what type of finder should i use? (byValueKey? bySemanticLabel? byType? or what?)
static final arrowKey = find.byValueKey(LoginKey.nextButton);
TestDriverUtil.tap(driver, arrowKey);
We have text and value checks here in Flutter Driver but if you don't have that you can always go the the hierarchy of app.
what I mean by hierarchy is so button has fix or specific parent right?
Let's take your example here, We have Align > Container > IconButton > Icon widget hierarchy which will not be true for others like there might be IconButton but not with the Container parent.
or StreamBuilder or anything that we can think of.
Widget _buildNextButton() {
return Align(
alignment: Alignment.bottomRight,
child: Container(
child: IconButton(
icon: Icon(Icons.arrow_forward),
onPressed: () => print("clicked button"),
),
),
);
}
This hierarchy should be atleast ideal for top bottom or bottom top approach.
Now what I mean by Top to bottom approach is Align must have IconButton and for bottom to up approach we are saying IconButton must have Align widget as parent.
Here i have taken top down approach so what I'm checking from below code is finding IconButton who is decendent of Align Widget.
also i added firstMatchOnly true as I was checking what happens if same hierarchy appears for both so
test('IconButton find and tap test', () async {
var findIconButton = find.descendant(of: find.byType("Align"), matching: find.byType("IconButton"), firstMatchOnly: true);
await driver.waitFor(findIconButton);
await driver.tap(findIconButton);
await Future.delayed(Duration(seconds: 3));
});
to check for multiple IconButtons with same Align as parent, we need to have some difference like parent should be having Text view or other widget.
find.descendant(of: find.ancestor(
of: find.byValue("somevalue"),
matching: find.byType("CustomWidgetClass")), matching: find.byType("IconButton"), firstMatchOnly: true)
usually I go something like above where I have split the code in seperate file and check for that widget.
But ultimately find something unique about that widget and you can work on that.
**In Lib directory dart class for connecting that widget**
class Testing extends StatelessWidget {
Testing();
// This widget is the root of your application.
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: YourClass(), // Next button containing class that need to test
);
}
}
**In Test directory**
testWidgets('Next widget field test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(Testing());
// find Widget
var buttonFind = find.byIcon(Icons.arrow_forward);
expect(buttonFind, findsOneWidget);
IconButton iconButton = tester.firstWidget(buttonFind);
expect(iconButton.color, Colors.blue);
});
I am currently learning app development with Flutter and have started learning about the Provider package. I was having some difficulty and was getting the error:
"Could not find the correct Provider above this ... Widget"
I ended up moving the Provider widget to wrap around my MaterialApp widget instead of my Scaffold Widget, and that seemed to fix things.
That being said, I'm not sure why this fixed things. Are we supposed to put our Provider widget around our MaterialApp? If so, can someone please explain why this is needed? If not, can someone explain how to determine where to place the Provider widget in our tree?
Usually, the best place is where you moved it, in the MaterialApp. This is because since that is where the app starts, the node tree will have access to the provider everywhere.
If your page is a Stateful widget - inside Widget wrap State with Provider, so you can use it inside of State. This is a much cleaner solution because you won't have to wrap your entire application.
If you need the functionality of Provider everywhere in the app - yes, wrapping the entire app is completely fine, though I'll prefer to use some kind of service for this
You could add it to any route and pass it to the route you need to use or you can add it to MaterialApp
so you can use it anywhere.
The best practice of using provider:
Place the Provider widget at the top of the widget tree. Bellow I put a template code that can be used for one more providers at the same place, by using MultiProvider widget under Provider package.
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ProviderName<ModelName>(create: (_) => ModelName()),
AnotherProviderName<AnotherModelName>(create: (_) => AnotherModelName()),
],
child: MaterialApp(
title: 'App title',
theme: ThemeData(
primarySwatch: Colors.blue,
primaryColor: const Color(0xFF2196f3),
accentColor: const Color(0xFF2196f3),
canvasColor: const Color(0xFFfafafa),
),
home: MyHomePage(), // Your widget starting
),
);
}
}
For more informatin: https://pub.dev/documentation/provider/latest/
I'm reading the docs: https://docs.flutter.io/flutter/widgets/BuildContext-class.html
This can lead to some tricky cases. For example, Theme.of(context) looks for the nearest enclosing Theme of the given build context. ...
Does enclosing includes the Theme of current context? I don't understand what the tricky cases the author indicated.
The tricky situation they mentioned becomes more clear if you understand the previous statement:
In particular, this means that within a build method, the build context of the widget of the build method is not the same as the build context of the widgets returned by that build method.
Ok, not a very helpful language. Imagine:
FooWidgetA:
attributes: context, height, width
methods: build
FooWidgetB:
attributes: context, theme
methods: build
FooWidgetB gets built inside FooWidgetA build method. If you try to find theme using the context of FooWidgetA it won't find because FooWidgetA is one level above in the widget tree.
So, exemplifying their tricky situation, it looks like this:
class Foo extends StatelessWidget {
#override
Widget build(BuildContext buildMethodContext) {
return MaterialApp(
// Here we create the [ThemeData] that our method below will try to find
theme: ThemeData(primaryColor: Colors.orange),
builder: (BuildContext materialAppContext, _) {
return RaisedButton(
child: const Text('Get ThemeData'),
onPressed: () {
getThemeData(buildMethodContext); // unsucessful
getThemeData(materialAppContext); // sucessful
},
);
},
);
}
ThemeData getThemeData(BuildContext context) => Theme.of(context);
}
It's tricky because, since the two contexts are way to close, it's easy to forget that buildMethodContext is actually from the parent (Foo), so it cannot see materialAppContext.
I was hoping to use InheritedWidget at the root level of my Flutter application to ensure that an authenticated user's details are available to all child widgets. Essentially making the Scaffold the child of the IW like this:
#override
Widget build(BuildContext context) {
return new AuthenticatedWidget(
user: _user,
child: new Scaffold(
appBar: new AppBar(
title: 'My App',
),
body: new MyHome(),
drawer: new MyDrawer(),
));
}
This works as expected on app start so on the surface it seems that I have implemented the InheritedWidget pattern correctly in my AuthenticatedWidget, but when I return back to the home page (MyHome) from elsewhere like this:
Navigator.popAndPushNamed(context, '/home');
This call-in the build method of MyHome (which worked previously) then results in authWidget being null:
final authWidget = AuthenticatedWidget.of(context);
Entirely possible I'm missing some nuances of how to properly implement an IW but again, it does work initially and I also see others raising the same question (i.e. here under the 'Inherited Widgets' heading).
Is it therefore not possible to use a Scaffold or a MaterialApp as the child of an InheritedWidget? Or is this maybe a bug to be raised? Thanks in advance!
MyInherited.of(context) will basically look into the parent of the current context to see if there's a MyInherited instantiated.
The problem is : Your inherited widget is instantiated within the current context.
=> No MyInherited as parent
=> crash
The trick is to use a different context.
There are many solutions there. You could instantiate MyInherited in another widget, so that the context of your build method will have a MyInherited as parent.
Or you could potentially use a Builder to introduce a fake widget that will pass you it's context.
Example of builder :
return new MyInheritedWidget(
child: new Builder(
builder: (context) => new Scaffold(),
),
);
Another problem, for the same reasons, is that if you insert an inheritedWidget inside a route, it will not be available outside of this route.
The solution is simple here !
Put your MyInheritedWidget above MaterialApp.
above material :
new MyInherited(
child: new MaterialApp(
// ...
),
)
Is it therefore not possible to use a Scaffold or a MaterialApp as the
child of an InheritedWidget?
It is very possible to do this. I was struggling with this earlier and posted some details and sample code here.
You might want to make your App-level InheritedWidget the parent of the MaterialApp rather than the Scaffold widget.
I think this has more to do with how you are setting up your MaterialWidget, but I can't quite tell from the code snippets you have provided.
If you can add some more context, I will see if I can provide more.