import 'package:flutter/material.dart';
class ThemeSwitcher extends InheritedWidget {
final _ThemeSwitcherWidgetState data; // We'll use ThemeSwitcher to get access to the current state of ThemeSwitcherWidget
const ThemeSwitcher({
Key key,
#required this.data,
#required Widget child,
}) : assert(child != null),
super(key: key, child: child);
static _ThemeSwitcherWidgetState of(BuildContext context) { //This method returns the current state of the ThemeSwitcherWidget. This will be used down the tree
return (context.dependOnInheritedWidgetOfExactType(ThemeSwitcher)
as ThemeSwitcher)
.data;
}
#override
bool updateShouldNotify(ThemeSwitcher old) {
return this != old;
}
}
class ThemeSwitcherWidget extends StatefulWidget {
final bool initialDarkModeOn; // this is the initial state of the variable
final Widget child; // child to which this boolean variable should be propagated upon change. This will be our app in this case
ThemeSwitcherWidget({Key key, this.initialDarkModeOn, this.child})
: assert(initialDarkModeOn != null),
assert(child != null),
super(key: key);
#override
_ThemeSwitcherWidgetState createState() => _ThemeSwitcherWidgetState();
}
class _ThemeSwitcherWidgetState extends State<ThemeSwitcherWidget> {
bool isDarkModeOn;
void switchDarkMode() { //method used to toggle dark mode during the runtime of the app
setState(() {
isDarkModeOn = !isDarkModeOn;
});
}
#override
Widget build(BuildContext context) {
isDarkModeOn = isDarkModeOn ?? widget.initialDarkModeOn; // this is the build method which would build the widget tree with the above info
return ThemeSwitcher(
data: this,
child: widget.child,
);
}
}
Too many positional arguments: 0 expected, but 1 found.
Try removing the extra positional arguments, or specifying the name for named arguments.
This is the Error I am continuously facing the issue after trying many methods.
I would like to know how would this problem can be solved as I am not getting any good solution from searches.
Return the following statement in _ThemeSwitcherWidgetState of(BuildContext context) method of your code:
return (context.dependOnInheritedWidgetOfExactType<ThemeSwitcher>()).data;
Related
I have a convenience StatelessWidget that returns the appropriate widget for one of three display size breakpoints:
/// Return the most appropriate widget for the current display size.
///
/// If a widget for current display size is not available, chose the closest smaller variant.
/// A [mobile] size widget is required.
class SizeAppropriate extends StatelessWidget {
// ignore: prefer_const_constructors_in_immutables
SizeAppropriate(
this.context,
{
required this.mobile,
this.tablet,
this.desktop,
Key? key
}
) : super(key: key);
final BuildContext context;
late final Widget mobile;
late final Widget? tablet;
late final Widget? desktop;
#override
Widget build(BuildContext context) {
switch (getDisplaySize(context)) {
case DisplaySize.mobile:
return mobile;
case DisplaySize.tablet:
return tablet ?? mobile;
case DisplaySize.desktop:
return desktop ?? tablet ?? mobile;
}
}
}
I then use it like this:
SizeAppropriate(
context,
mobile: const Text('mobile'),
desktop: const Text('desktop'),
)
Is the keyword late working here as intended, building only the correct variant, or am I hogging the performance, because all variants are constructed (or am I even creating an anti-pattern)?
Should I use builder functions instead?
Edit:
This answer makes me think I'm correct. Although the last two sentences make me think I'm not correct.
When you do this, the initializer becomes lazy. Instead of running it as soon as the instance is constructed, it is deferred and run lazily the first time the field is accessed. In other words, it works exactly like an initializer on a top-level variable or static field. This can be handy when the initialization expression is costly and may not be needed.
When I do log('mobile') and log('desktop') in the respective widgets being constructed, I only get one type of message in the console.
Edit 2 – minimum working example:
This is my main.dart in a newly generated project (VS Code command >Flutter: New Project – Application). It might be worth noting, that I am testing this on Linux.
import 'package:flutter/material.dart';
import 'dart:developer';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: SizeAppropriate(
context,
mobile: const Mobile(),
tablet: const Tablet(),
desktop: const Desktop(),
),
),
);
}
}
class Mobile extends StatelessWidget {
const Mobile({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
log('mobile');
return const Text('mobile');
}
}
class Tablet extends StatelessWidget {
const Tablet({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
log('tablet');
return const Text('tablet');
}
}
class Desktop extends StatelessWidget {
const Desktop({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
log('desktop');
return const Text('desktop');
}
}
enum DisplaySize {
desktop,
tablet,
mobile,
}
DisplaySize getDisplaySize(BuildContext context) {
Size size = MediaQuery.of(context).size;
if (size.width < 768) {
return DisplaySize.mobile;
}
else if (size.width < 1200) {
return DisplaySize.tablet;
}
else {
return DisplaySize.desktop;
}
}
class SizeAppropriate extends StatelessWidget {
// ignore: prefer_const_constructors_in_immutables
SizeAppropriate(
this.context,
{
required this.mobile,
this.tablet,
this.desktop,
Key? key
}
) : super(key: key);
final BuildContext context;
late final Widget mobile;
late final Widget? tablet;
late final Widget? desktop;
#override
Widget build(BuildContext context) {
switch (getDisplaySize(context)) {
case DisplaySize.mobile:
return mobile;
case DisplaySize.tablet:
return tablet ?? mobile;
case DisplaySize.desktop:
return desktop ?? tablet ?? mobile;
}
}
}
late does not do what you want. It's only for the null-safety feature and when you do or don't get warnings about it. Those two texts get built every time regardless of environment, because they need to be there when they are passed to your widget.
If you to only build the appropriate widgets for each size when the size is known, you indeed need indeed pass two builders, one of which you call if appropriate.
For two constant Text widgets, that would be too much overhead, but I am assuming you want "heavier" widget trees for both options in the end.
I have a app_bar_base.dart file where i have an AppBar.
class AppBarBase extends StatelessWidget implements PreferredSizeWidget {
late double appBarHeight = LoadAppStyle().loadAppStyle();
AppBarBase({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return AppBar();
}
#override
Size get preferredSize => Size.fromHeight(appBarHeight);
}
I am calling the method LoadAppStyle().loadAppStyle() from the file load_app_style:
class LoadAppStyle {
loadAppStyle() async {
String jsonData =
await rootBundle.loadString('assets/config/app_style.json');
Map<String, dynamic> data = jsonDecode(jsonData);
var getHeight = double.parse(data["app_bar"]["app_bar_height"]);
return getHeight;
}
}
In the load_app_style.dart file i grab the value of app_bar_heigt from the app_style.json
in app_style.json i have key app_bar_height where i want to change the value manually to change the height of the App bar
{
"app_bar":
{
"app_bar_height": 78
},
}
But for some reason i get the error : type 'Future<dynamic>' is not a subtype of type 'double'
You can add the type to your loadAppStyle method. Since your method is async it returns a Future.
Future<double> loadAppStyle() async {
...
return getHeight;
}
Now your error should be
type 'Future<double>' is not a subtype of type 'double'
Since your method returns a Future you have to use await to get the value.
loadAppStyle() // Future<double>
await loadAppStyle() // double
If you want to use a value of a Future inside a Widget, have a look at FutureBuilder.
For your case you could e.g. use the FutureBuilder to retrieve the height and then pass it to AppBarBase
FutureBuilder<double>(
future: loadAppStyle(),
builder: (context, snapshot) {
if(snapshot.hasData) {
return AppBarBase(height: snapshot.data);
} else {
return const Center(child: CirclularProgressIndicator));
}
}
)
And change your AppBarBase to the following.
class AppBarBase extends StatelessWidget implements PreferredSizeWidget {
AppBarBase({
Key? key,
required this.height,
}) : super(key: key);
final double height;
#override
Widget build(BuildContext context) {
return AppBar();
}
#override
Size get preferredSize => Size.fromHeight(height);
}
In your example, loadAppStyle() has no defined return type (dynamic) and it is marked as async (Future), hence the return type of this function is Future<dynamic>. Size.fromHeight function requires the double value, hence you get this error - the expected type is double, but Future<dynamic> was found here.
To resolve the type differences, you should set the return type of a function:
class LoadAppStyle {
Future<double> loadAppStyle() async {
String jsonData =
await rootBundle.loadString('assets/config/app_style.json');
Map<String, dynamic> data = jsonDecode(jsonData);
var getHeight = double.parse(data["app_bar"]["app_bar_height"]);
return getHeight;
}
}
Now, since your function is async, you must wait for your Future to finish and only then you could retrieve the double value. It would look something like this:
class AppBarBase extends StatelessWidget implements PreferredSizeWidget {
late double appBarHeight = await LoadAppStyle().loadAppStyle(); // Throws error
AppBarBase({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return AppBar();
}
#override
Size get preferredSize => Size.fromHeight(appBarHeight);
}
However, this throws an error since you cannot use the asynchronous code when initialising a value this way. What could be a better way to do this is to wait for this value somewhere outside of your widget and pass the result via the constructor:
class AppBarBase extends StatelessWidget implements PreferredSizeWidget {
final double appBarHeight;
AppBarBase({
required this.appBarHeight,
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return AppBar();
}
#override
Size get preferredSize => Size.fromHeight(appBarHeight);
}
This way, you separate your UI code from the widget. Anyway, the way of keeping this UI-specific configuration inside the JSON file sounds way overengineered - consider just passing this value via constructor directly, like: AppBarBase(appBarHeight: 78).
I am using the ScopedModel pattern, but I am also interested how this same problem is addressed in the similar Provider pattern.
Currently I have a ScopedModel with a bool exposed called loggedIn. When the FirebaseonAuthStateChanged stream changes user log in state, my ScopedModel changes that bool, and calls NotifyListeners. All straight forward stuff.
Now I am confused as to the best way to push or pop routes based on this ScopedModel.
Should all my logged in screens (screens that require a user) have the following code in build method?
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!auth.hasUser)
Navigator.of(context).pushNamedAndRemoveUntil('/entry', (Route<dynamic> route) => });
});
That seems a little excessive to have this code on every single screen. Is there a way I can define this log screen change behaviour somewhere only once?
create a Widget for it ;)
class Validation extends StatefulWidget {
final Function validator;
final Widget child;
const Validation({Key key, this.validator, this.child}) : super(key: key);
#override
_ValidationState createState() => _ValidationState();
}
class _ValidationState extends State<Validation> {
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
widget.validator();
});
super.initState();
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
now use it everywhere
#override
Widget build(BuildContext context) {
return Validation(
validator: (){
if (!auth.hasUser){
Navigator.of(context).pushNamedAndRemoveUntil('/entry', (Route<dynamic> route) => false);
}
},
child: MyAwesomePage(),
);
}
you can further simplify if the validation is same everywhere or create multiple validation widget according to the validations required,
FOR YOUR CASE
class LoginValidation extends StatefulWidget {
final String routeIfNotLoggedIn;
final Widget child;
const LoginValidation({Key key, this.routeIfNotLoggedIn, this.child}) : super(key: key);
#override
_LoginValidationState createState() => _LoginValidationState();
}
class _LoginValidationState extends State<LoginValidation> {
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (!auth.hasUser){
Navigator.of(context).pushNamedAndRemoveUntil(widget.routeIfNotLoggedIn, (Route<dynamic> route) => false);
}
});
super.initState();
}
}
and use it
#override
Widget build(BuildContext context) {
return LoginValidation(
routeIfNotLoggedIn: "/myLoginRoute",
child: MyAwesomePage(),
);
}
I am so confused about state management.
Below is I pass down data through widgets.
List<AppUser> userList = List<AppUser>();
List<List<MessageType>> messageLists = new List<List<MessageType>>();
#override
void initState() {
super.initState();
loadUsers();
}
Future<void> loadUsers() async {
userList.clear();
userList.addAll(await AppUser.getRelatedUsers(customer.customerID));
defaultUser = await AppUser.getDefaultUser(customer.customerID);
if (defaultUser != null && !await defaultUser.hideUserTab()) {
userList.add(defaultUser);
}
await loadMessageList();
}
Then I pass the userList and messageList to another stateful widget. But what if I want to have those data through the whole app using inherited widget or provider or bloc.
MessageTypePage(
messageTypeList: messageLists[tabIndex],
currentUser: userList[tabIndex],
);
How can I possible to get the data from db and store them in inherited widget then using those data? I am so confused.
class StateContainer extends StatefulWidget {
final Widget child;
final List<AppUser> userList;
final List<Message> messageList;
StateContainer({#required this.child, this.userList, this.messageList});
static StateContainerState of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<_InheritedStateContainer>().data;
}
#override
StateContainerState createState() => new StateContainerState();
}
class StateContainerState extends State<StateContainer> {
List<AppUser> userList = List<AppUser>();
List<List<MessageType>> messageLists = new List<List<MessageType>>();
#override
Widget build(BuildContext context) {
return _InheritedStateContainer(
data: this,
child: widget.child,
);
}
}
class _InheritedStateContainer extends InheritedWidget {
final StateContainerState data;
_InheritedStateContainer({Key key, #required this.data, #required Widget child}) : super(key: key, child: child);
#override
bool updateShouldNotify(_InheritedStateContainer oldWidget) {
return true;
}
}
In my opinion, the best approach is to use Provider or Bloc. There is a flutter codelab that uses Provider to do something very similar to what you are doing. It stores a list of items (in your case that would be Users) that can be used throughout the app. It also shows you how to manipulate the list in various ways.
The codelab is here. I think it would help you out.
When I use VSCode snippet Extract Widget, I have the following :
class MyExtractedWidget extends StatelessWidget {
const MyExtractedWidget({
Key key,
#required T someVariable,
}) : _someVariable = someVariable,
super(key: key);
final T _someVariable;
#override
Widget build(BuildContext context){ return Container(); }
}
However, I am used to write constructors the following way :
class MyExtractedWidget extends StatelessWidget {
const MyExtractedWidget({
Key key,
#required this.someVariable, // Directly accessing variable using "this"
}) : super(key: key);
final T someVariable;
#override
Widget build(BuildContext context){ return Container(); }
}
Do you know why snippets' constructors use a temporary variable instead of directly writing in the variable?
Is it related to encapsulation? If yes, I cannot understand why, as an extracted Widget is written in the same file, and that "underscored" variables are accessible in whole file.
EDIT
I tried with another widget and I have a kind of mix :
class Test extends StatelessWidget {
const Test({
Key key,
#required List<SortedExpense> sortedExpenses,
#required this.expensesSink,
}) : _sortedExpenses = sortedExpenses, super(key: key);
final List<SortedExpense> _sortedExpenses;
final StreamSink<List<Expense>> expensesSink;
...
This is based on the privacy of the variables you're extracting.
For example, the following widget:
Text(_count.toString())
will generate:
class MyName extends StatelessWidget {
const MyName({
Key key,
#required int count,
}) : _count = count, super(key: key);
final int _count;
#override
Widget build(BuildContext context) {
return Text(_count.toString());
}
}
while this widget:
Text(count.toString())
will create:
class MyName extends StatelessWidget {
const MyName({
Key key,
#required this.count,
}) : super(key: key);
final int count;
#override
Widget build(BuildContext context) {
return Text(count.toString());
}
}