How can I use MediaQuery to build theme? - flutter

started to learn Flutter so I'm thinking if is it possible to create a function that returns a ThemeData object, but inside this function I wanna use MediaQuery.of(context). I mean, I know I can create such a function, but if I use MediaQuery.of(context) inside of it, I have an error
(MediaQuery.of() called with a context that does not contain a MediaQuery) complaining about the missing of MediaQueryProvider. I know I can use MediaQuery in child elements of MaterialApp, but I have a design problem now. Imagine this:
ThemeData getTheme(BuildContext context) {
// I wanna be able to call MediaQuery.of(contex) here
return ThemeData();
}
MaterialApp(
home: home,
// the getTheme() must be able to use MediaQuery. It takes a context and returns a ThemeData object
theme: theme.getTheme(context),
routes: routes,
)
Is there a way to do it? or event better, Should I do it?
Thank you for your help.

A workaround is not using the theme property of MaterialApp but wrapping the descendants with a Theme widget using the builder property. It will have the MaterialApp as an ancestor so you can use MediaQuery there.
MaterialApp(
builder: (context, child) {
return Theme(
data: getTheme(context), // you can use MediaQuery.of(context) in the called function
child: child,
);
},
home: HomeScreen(),
// etc, no theme here
)

Welcome to the brotherhood.
Actually, to call for MediaQuery you will need to wrap your main widget with a MaterialApp() which usually is done in your main.dart file.
Here is an example:
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
Hope it helped!

You can try this.
Create a stateless/ stateful widget for the home of your MaterialApp, and inside that, create your getTheme method.
Example,
homePage extends StatelessWidget{
ThemeData getTheme(BuildContext context) {
// Call MediaQuery.of(contex) here
return ThemeData();
}
#override
//build method
}//homePage ends here.
Now, in your material app, simple call that method.
MaterialApp(
home: homePage(),
theme: homePage().getTheme(context),
routes: routes,
)

Related

How can I make a card clickable and navigate to another page inside TabBarView?

I'm trying to make a card clickable and navigate to another page 'forecastWeather.dart'. But I can't seem to figure out how I can fix the error I'm getting. The error message says
Another exception was thrown: Navigator operation requested with a context that does not include a Navigator.
I'm using a TabBarView with two tabs and one contains a card which should be clickable and navigate to another page 'forecastWeather.dart'. Please help!
In order to get use navigator.push, you must pass the context of your widget and have a MaterialApp somewhere in your tree, you do both, so the code should work, but it doesn't.
The reason is simple, the context you are passing is outdated, you are first creating the context when the build method begins, at that point, there is no material app, then you add a material app, but the context already exists, so the context you are passing when calling Navigator.push(context ... has no MaterialApp.
A possible fix is to use the Builder widget to make a new context after you create the MaterialApp widget:
#override
Widget build(BuildContext context) {
// context has no material app.
return MaterialApp(
home: Builder(
builder: (context) {
// this context does have a material app and is safe to use.
return DefaultTabController( ... );
}
),
);
}
Another fix is to move the code into a new widget with it's own context:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainPage(),
);
}
}
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
// this context was created after the material app.
return DefaultTabController(...);
}
}
i suggest you to use get: ^4.6.1 package.
with this package you just need to use this for any navigation:
Get.to(ForecastWeather());
this package have so other features read its document here
[1]: https://pub.dev/packages/get

What is the difference between passing a widget class and simply passing a function which return a widget? [duplicate]

This question already has answers here:
What is the difference between functions and classes to create reusable widgets?
(6 answers)
Closed 7 months ago.
It is quite often to see people passing a widget class or call a function which return a widget to Body property of a widget.
What confuse me is that both are actually work. But I dont understand the reason behind when to choose which to implement
For example,
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
**body: _buildContents(context),**
),
);
}
}
vs
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
**body: HomePage(),**
),
);
}
}
The main difference between these two is that you could use const with Widget constructors, but you cannot add const to a function call. E.g.:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: _buildContents(context), // you cannot add const here
),
);
}
}
vs
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: const HomePage(), // If available, you can add const here
),
);
}
}
That is important for performance. When rebuilding the Widget tree, Widgets with const constructors won't rebuild - they are constants, right? However, with function calls you cannot ensure that - function body would be executed every single time on rebuild.
You could imagine that being a huge problem when this specific Widget is quite expensive to build and you are running your app, let's say, 60 fps - your "expensive" function would be called 60 times per second, which could lead to UI rendering janks in some cases.
The rule of thumb: use Widgets with const constructors wherever you can.
a widget class
It's not a widget class, it's a widget constructor.
call a function
here it's insert not a function call, but a result of a function call.
There are plenty of reasons why to use build functions instead of a widget constructor. Some of them:
to make code readable and clean. If you have a huge widget tree it's difficult to find some widgets, you need to spend some time to find exact place of a widget you are looking for. It's easy to understand what widget it is, because now it has a name. And so on. Also, when a widget tree is complex it's very difficult to read it and deal with it;
sometimes widgets can be used several times (sometimes they are slightly differ) in the widget tree, so instead of copy all the code, we can just refer to a pre-setuped widget throw widget build function. Let's imagine, that in such a case we need to change something in our "twin" widgets. If we have a build widget function we can do it only in one place, otherwise we are to look over the code and make changes in several places;
sometimes what widget to use depends on some logic. It's a good idea to isolate this somewhere in the function instead of placing this logic inside the widget tree, otherwise it's complicating the code and widget tree, making difficulties to work with;
...

Get the Global Context in Flutter

Is there any way to get the global context of Material App in Flutter. Not the context of particular screen.
I am trying to get the context but it gives me the context of particular screen but I wan the context of MaterialApp.
If above solution does not works please try this solution.
Create the class. Here it named as NavigationService
import 'package:flutter/material.dart';
class NavigationService {
static GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
}
Set the navigatorKey property of MaterialApp in the main.dart
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: NavigationService.navigatorKey, // set property
)
}
Great! Now you can use anywhere you want e.g.
print("---print context:
${NavigationService.navigatorKey.currentContext}");
Assign a GlobalKey() to the MaterialApp which you can put in a separate class, let's call it App :
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: App.materialKey, // GlobalKey()
)
}
Now wherever you want to get the context of the MaterialApp, you just have to call :
App.materialKey.currentContext
Here I'm printing MaterialApp context :
print('Material App Context : ${App.materialKey.currentContext}');
OUTPUT :
flutter: Material App Context : MaterialApp-[GlobalKey#4fab4](state: _MaterialAppState#4bb44)
If you use Getx State Management you can use;
GetMaterialApp(
navigatorKey: Get.key,
);
Reach out with;
BuildContext? context = Get.key.currentContext;

is it okay to have two material apps in one flutter?

I added two material app to my app because my futurebuilder needed a context and my provider was not accessible to the other classes i created. is it an acceptable practice???
runApp(
MaterialApp(
title: 'register app',
home: FutureBuilder(
future: Hive.openBox('store'),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError)
return Text(snapshot.error.toString());
else
return MultiProvider(providers: [
ChangeNotifierProvider.value(
value: form_entry(),
)
], child: MyApp());
} else
return Scaffold(
body: Center(
child: Text('error error ooops error'),
));
},
)),
);[enter image description here][1]
// my app class has another material app
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
home: home_screen(),
);
}
}
The purpose of a MaterialApp widget is to provide a common theme setting based on Material design and configures the root navigator for all of its children widgets.
In order to avoid conflicting, you should only have 1 MaterialApp. In your case, you can call the openBox() method without using the FutureBuilder by calling it within the main() method:
void main() async {
// Include this line to make sure WidgetsFlutterBinding is initialized, since
// we're using main() asynchronously
WidgetsFlutterBinding.ensureInitialized();
// Open the Hive box here
var box = await Hive.openBox('store');
// Then run the app
runApp(
MaterialApp(
title: 'register app',
home: MultiProvider(providers: [
ChangeNotifierProvider.value(
value: form_entry(),
)
], child: home_screen());
)
);
Small note: When creating new class or method in Dart, best practice is to use CamelCase. So form_entry() should be named FormEntry() for Class name or formEntry() for Method name. Same goes with home_screen(). You can refer to the styling guide here
It's bad to have two MaterialApp() widgets, at least one in other.
I did that by mistake, I thought I do not have one and added an extra one. Then the app randomly crashed on hot restart, sit one whole day to debug everything and haven't found what crashed my app, then I started to refactor code and found I have two MaterialAapp, one in StatelessWidget and one in home widget that was on different file. After removing it my app stopped randomly crashing.
Never use two, atleast not one in other.

Opening keyboard causes stateful widgets to be re-initialized

I am using Flutter 1.2.1 in the Stable branch. To illustrate my problem imagine I have pages A and B. A navigates to B using Navigator.push and B navigates back to A using Navigator.pop. Both are stateful widgets.
When I navigate from A to B and then pop back to A everything is fine and A keeps its state. However, if I navigate from A to B, tap a textfield in B opening the keyboard, then close the keyboard and pop back to A, A's entire state is refreshed and the initState() method for A is called again. I verified this by using print statements.
This only happens when I open the keyboard before popping back to A. If I navigate to B, then immediately navigate back to A without interacting with anything then A keeps its state and is not re-initialized.
From my understanding the build method is called all the time but initState() should not get called like this. Does anyone know what is going on?
After much trial and error I determined the problem. I forgot that I had setup a FutureBuilder for the / route in my MaterialApp widget. I was passing a function call that returns a future to the future parameter of the FutureBuilder constructor rather than a variable pointing to a future.
So every time the routes got updated a brand new future was being created. Doing the function call outside of the MaterialApp constructor and storing the resulting future in a variable, then passing that to the FutureBuilder did the trick.
It doesn't seem like this would be connected to the weird behavior I was getting when a keyboard opened, but it was definitely the cause. See below for what I mean.
Code with a bug:
return MaterialApp(
title: appTitle,
theme: ThemeData(
primarySwatch: Colors.teal,
accentColor: Colors.tealAccent,
buttonColor: Colors.lightBlue,
),
routes: {
'/': (context) => FutureBuilder<void>(
future: futureFun(), //Bug! I'm passing a function that returns a future when called. So a new future is returned each time
builder: (context, snapshot) {
...
}
...
}
...
}
Fixed Code:
final futureVar = futureFun(); //calling the function here instead and storing its future in a variable
return MaterialApp(
title: appTitle,
theme: ThemeData(
primarySwatch: Colors.teal,
accentColor: Colors.tealAccent,
buttonColor: Colors.lightBlue,
),
routes: {
'/': (context) => FutureBuilder<void>(
future: futureVar, //Fixed! Passing the reference to the future rather than the function call
builder: (context, snapshot) {
...
}
...
}
...
}
did you use AutomaticKeepAliveClientMixin in "A" widget ?
if you don't , see this https://stackoverflow.com/a/51738269/3542938
if you already use it , please give us a code that we can test it directly into "main.dart" to help you
Yup, happened to me, perhaps it's much better to wrap the FutureBuilder itu a PageWidget, and make it singleton
return MaterialApp(
title: appTitle,
theme: ThemeData(
primarySwatch: Colors.teal,
accentColor: Colors.tealAccent,
buttonColor: Colors.lightBlue,
),
routes: {
'/': (context) => PageWidget() // wrap it by PageWidget
...
}
...
}
class PageWidget extends StatelessWidget {
static final _instance = PageWidget._internal(); // hold instance
PageWidget._internal(); // internal consturctor
factory PageWidget() {
return _instance; // make it singleton
}
#override
Widget build(BuildContext context) {
return FutureBuilder<void>( ... );
}
}
I got a solution, I was initialising variables in the constructor of the superclass. I removed it and worked!
I just removed the FutureBuilder from the home of MaterialApp and changed the MyApp into a Stateful widget and fetched the requisite info in the initState and called setState in the .then(); of the future and instead of passing multiple conditions in the home of MaterialApp, I moved those conditions to a separate Stateful widget and the issue got resolved.
initState:
#override
void initState() {
// TODO: implement initState
// isSignedIn = SharedPrefHelper.getIsSignedIn();
getIsSignedInFromSharedPreference().then((value) {
setState(() {
isSignedInFromSharedPref = value ?? false;
if (isSignedInFromSharedPref) {
merchantKey = LocalDatabase.getMerchantKeyWithoutAsync();
}
isLoadingSharedPrefValue = false;
});
});
super.initState();
}
Future<bool?> getIsSignedInFromSharedPreference() async {
return SharedPrefHelper.getIsSignedIn();
}
MaterialApp (now):
MaterialApp(
title: 'Loveeatry POS',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Home(
isLoadingSharedPrefValue: isLoadingSharedPrefValue,
isSignedInFromSharedPref: isSignedInFromSharedPref,
merchantKey: merchantKey,
),
),
Home:
class Home extends StatelessWidget {
final bool isLoadingSharedPrefValue;
final bool isSignedInFromSharedPref;
final String merchantKey;
const Home({
Key? key,
required this.isLoadingSharedPrefValue,
required this.isSignedInFromSharedPref,
required this.merchantKey,
}) : super(key: key);
#override
Widget build(BuildContext context) {
if (!isLoadingSharedPrefValue) {
if (isSignedInFromSharedPref) {
return const Homepage(
shouldLoadEverything: true,
);
} else if (merchantKey.isNotEmpty) {
return LoginPage(merchantKey: merchantKey);
} else {
return const AddMerchantKeyPage();
}
} else {
return loading(context);
}
}
}
P.S.: If you need any more info, please leave a comment.