Is it bad to have MaterialApp's home be a MaterialApp? - flutter

I'm working on a multi-page application, and currently have this:
void main() {
runApp(const MaterialApp(
title: "App", debugShowCheckedModeBanner: false, home: HomePage()));
}
And over in HomePage, I return a Material App
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(); //My home page
}
}
I looked online for if the home property of a MaterialApp should return another MaterialApp, as HomePage currently does, or if it should return a Scaffold, and I couldn't really find anything. I would think that a Scaffold makes a bit more sense, since that's what I would typically do in a single-page app, but the only thing that gives me pause is that in terms of syntax, making a new "MaterialApp" for each page and changing which is displayed sounds pretty nice-but I could very well be wrong.
Thanks!

A material app is used to define theme, route etc. So for the whole app it is good to have just one material app. Define all themes etc in it and have all other classes return a scaffold that way you have better control over the app..
More about material app
https://api.flutter.dev/flutter/material/MaterialApp-class.html
More about scaffold
https://api.flutter.dev/flutter/material/Scaffold-class.html

First of all, I don't recommend you to have two MaterialApp widgets, since you set properties in the first MaterialApp, meaning that the BuildContext of Flutter will not get those properties since there is a second MaterialApp widget lower in the widget tree.
What I would recommend you, is to directly pass your HomePage widget in the runApp function. That will allow you more clarity and visibility of your widgets:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "App", debugShowCheckedModeBanner: false, home: HomePage())
}
}
Then, in HomePage and all other pages that are views, you can return a Scaffold.

Related

cannot navigate next page

Does anyone know why my navigation page isnt working after I click the icon Parking which will lead me to other pages which will display an appbar.
I can not test your code but I think this happens because you are trying to navigate between two different app roots. Try removing the MaterialApp widget from your "parkingscreen.dart" file. Keep only the Scaffold widget since it contains all material components for you to work. Let me know if it works!
Please have this kind of code in your ParkingScreen.dart file
class ParkingScreen extends StatelessWidget {
const ParkingScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Parking Screen')),
body: SafeArea(child: Column()),
);
}
}
This happens because you are trying to navigate between two different app roots

Declare AppBar in another dart file and instantiate it with an different file

It's the 4th day of my Flutter. I used to use Unity.
Currently my project has 3 pages; Home, Profile, Settings
These pages are transitioned using Navigator.
The AppBar is declared in the Scaffold within each page, but the AppBar for all pages is the same.
So I have to write three of the same code. (always need to write and modify the same code as the number of pages increases in the future)
So I thought it looks good to declare AppBar as a variable and the page would just call it.
Like this:
//originappbar.dart
AppBar originAppBar(
backgroundColor: .....
)
//home.dart
class ShowHome extends StatelessWidget {
ShowHome({Key? key}) : super(key: key);
static const String route = '/home';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: originAppBar(), ....
//profile.dart
//Same as home.dart
In the above example, I'm trying to assign the originAppBar created by originappbar.dart to appbar: in home.dart or profile.dart.
But I can't declare AppBar as a variable.
I'm confused because I thought I could make AppBar a variable.
What's wrong with my code?
Thank you.
PreferredSizeWidget originAppBar = new AppBar(
backgroundColor: .....
)
//home.dart
class ShowHome extends StatelessWidget {
ShowHome({Key? key}) : super(key: key);
static const String route = '/home';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: originAppBar, ....
//profile.dart
//Same as home.dart
You can create a stateless widget like:
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget{
///Here goes your duplicated code
}
And just use that CustomAppBar everywhere.
Or create a widget CustomScaffold that is nothing more than the scaffold that you use in all your pages but with the CustomAppbar and any other hanges made.

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;
...

How to overlay a widget on top of a Flutter app with named routes?

In my Flutter app (Android and iOS only), I want to add a widget that will overlay the entire app.
Here is what my main class looks like:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: "/someroute"
onGenerateRoute: MyRouteManager.generateRoute
);
}
I tried to follow that example, but my problem is that I use named routes in my app, and I don't know how to overlay a widget above my entire app in that case.
I also tried the overlay_screen plugin, but it has some bugs and does not allow the user to interact with the screen below the overlay screen.
How can I achieve that?
Thanks.
try this out:
Navigator.pushNamed(context, '/name_of_your_second_page');

Preserve widget state when temporarily removed from tree in Flutter

I'm trying to preserve the state of a widget, so that if I temporarily remove the stateful widget from the widget tree, and then re-add it later on, the widget will have the same state as it did before I removed it. Here's a simplified example I have:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
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> {
bool showCounterWidget = true;
#override
Widget build(BuildContext context) {
return Material(
child: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
showCounterWidget ? CounterButton(): Text("Other widget"),
SizedBox(height: 16,),
FlatButton(
child: Text("Toggle Widget"),
onPressed: (){
setState(() {
showCounterWidget = !showCounterWidget;
});
},
)
],
),
),
);
}
}
class CounterButton extends StatefulWidget {
#override
_CounterButtonState createState() => _CounterButtonState();
}
class _CounterButtonState extends State<CounterButton> {
int counter = 0;
#override
Widget build(BuildContext context) {
return MaterialButton(
color: Colors.orangeAccent,
child: Text(counter.toString()),
onPressed: () {
setState(() {
counter++;
});
},
);
}
}
Ideally, I would not want the state to reset, therefor the counter would not reset to 0, how would I preserve the state of my counter widget?
The reason why the widget loose its state when removed from the tree temporarily is, as Joshua stated, because it loose its Element/State.
Now you may ask:
Can't I cache the Element/State so that next time the widget is inserted, it reuse the previous one instead of creating them anew?
This is a valid idea, but no. You can't.
Flutter judges that as anti-pattern and will throw an exception in that situation.
What you should instead do is to keep the widget inside the widget tree, in a disabled state.
To achieve such thing, you can use widgets like:
IndexedStack
Visibility/Offstage
These widgets will allow you to keep a widget inside the widget tree (so that it keeps its state), but disable its rendering/animations/semantics.
As such, instead of:
Widget build(context) {
if (condition)
return Foo();
else
return Bar();
}
which would make Foo/Bar loose their state when switching between them
do:
IndexedStack(
index: condition ? 0 : 1, // switch between Foo and Bar based on condition
children: [
Foo(),
Bar(),
],
)
Using this code, then Foo/Bar will not loose their state when doing a back and forth between them.
Widgets are meant to store transient data of their own within their scope and lifetime.
Based on what you have provided, you are trying to re-create CounterButton child widget, by removing and adding it back to the widget tree.
In this case, the counter value that is under the CounterButton was not saved or not saving in the MyHomePage screen, the parent widget, without any reference to a view model or any state management within or at the top level.
A more technical overview how Flutter renders your widgets
Ever wonder what is the key if you try to create a constructor for a widget?
class CounterButton extends StatefulWidget {
const CounterButton({Key key}) : super(key: key);
#override
_CounterButtonState createState() => _CounterButtonState();
}
keys (key) are identifiers that are automatically being handled and used by the Flutter framework to differentiate the instances of widgets in the widget tree. Removing and adding the widget (CounterButton) in the widget tree resets the key assigned to it, therefore the data it holds, its state are also removed.
NOTE: No need to create constructors for the a Widget if it will only contain key as its parameter.
From the documentation:
Generally, a widget that is the only child of another widget does not need an explicit key.
Why does Flutter changes the key assigned to the CounterButton?
You are switching between CounterButton which is a StatefulWidget, and Text which is a StatelessWidget, reason why Flutter identifies the two objects completely different from each other.
You can always use Dart Devtools to inspect changes and toggle the behavior of your Flutter App.
Keep an eye on #3a4d2 at the end of the _CounterButtonState.
This is the widget tree structure after you have toggled the widgets. From CounterButton to the Text widget.
You can now see that the CounterButton ending with #31a53, different from the previous identifier because the two widgets are completely different.
What can you do?
I suggest that you save the data changed during runtime in the _MyHomePageState, and create a constructor in CounterButton with a callback function to update the values in the calling widget.
counter_button.dart
class CounterButton extends StatefulWidget {
final counterValue;
final VoidCallback onCountButtonPressed;
const CounterButton({Key key, this.counterValue, this.onCountButtonPressed})
: super(key: key);
#override
_CounterButtonState createState() => _CounterButtonState();
}
class _CounterButtonState extends State<CounterButton> {
#override
Widget build(BuildContext context) {
return MaterialButton(
color: Colors.orangeAccent,
child: Text(widget.counterValue.toString()),
onPressed: () => widget.onCountButtonPressed(),
);
}
}
Assuming you named your variable _counterValue in the _MyHomePageState, you can use it like this:
home_page.dart
_showCounterWidget
? CounterButton(
counterValue: _counterValue,
onCountButtonPressed: () {
setState(() {
_counterValue++;
});
})
: Text("Other widget"),
In addition, this solution will help you re-use CounterButton or other similar widgets in other parts of your app.
I've added the complete example in dartpad.dev.
Andrew and Matt gave a great talk how Flutter renders widgets under the hood:
https://www.youtube.com/watch?v=996ZgFRENMs
Further reading
https://medium.com/flutter-community/flutter-what-are-widgets-renderobjects-and-elements-630a57d05208
https://api.flutter.dev/flutter/widgets/Widget/key.html
The real solution to this problem is state management. There are several good solutions for this available as concepts and flutter packages. Personally I use the BLoC pattern regularly.
The reason for this is that widget state is meant to be used for UI state, not application state. UI state is mostly animations, text entry, or other state that does not persist.
The example in the question is application state as it is intended to persist longer than the live time of the widget.
There is a little Tutorial on creating a BLoC based counter which could be a good starting point.