Flutter Widget In Detail: MaterialApp
Detailed Explanation of MaterialApp Widget

Search for a command to run...
Detailed Explanation of MaterialApp Widget

No comments yet. Be the first to comment.
In this series, I am going to explain all the important Flutter widgets in detail.
Detailed Explanation of AbsorbPointer & IgnorePointer Widgets
Lessons from Code, Content, and Consistency

Reliability basics: Outbox pattern + why it matters

Introducing Kafka/Redpanda + move to event-driven workflow

Connect Orders ↔ Inventory (first working service-to-service flow)

Inventory service + gRPC + proto contracts



Another thing I want to point out is that MaterialApp and CupertinoApp are built upon WidgetApp.
import 'package:flutter/material.dart';
AppBar, Scaffold, BottomNavigationBar, Card, Chip, BottomSheet, etc.home, routes, onGenerateRoute, or builder properties non-null. Without it you will get an error.import 'package:flutter/material.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp();
}
}

Now let's take a deep dive into all the properties, and understand what each and every property does.
home:route of an app.Widget as an input.MaterialApp(
home: MyFirstPage(),
);

title:String as value.title, you will not see any changes in your app. It will still show an empty blank screen.title in MaterialAppMaterialApp(
title: "Widget In Detail",
home: MyFirstPage(),
);
debugShowCheckedModeBanner:true.false inside it.MaterialApp(
debugShowCheckedModeBanner: true,
title: "Widget In Detail",
home: MyFirstPage(),
);

builder :builder function takes two parameter context and widget.builder is Widget.MaterialApp(
builder: (context,widget) {
return widget;
}
);
builder property, we can override properties like Navigator, MediaQuery, or internationalization that is set by MaterialAppMaterialApp constructor using home, routes, onGenerateRoute, or onUnknownRoute, the child will be null, and it is the responsibility of the builder to provide the application's routing machinery.builder is null, routes must be provided using one of the other properties (home, routes, onGenerateRoute, or onUnknownRoute,).Use cases :
- To insert widgets above the
Navigator.- To insert widgets above the
Routerbut below the other widgets created by theWidgetsAppwidget- For replacing the
Navigator/Routerentirely.
Navigator is not provided in the builder we will not able to use Navigator.push, Navigator.pop, Hero etc.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, child) {
return MyHomePage();
});
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => SecondPage(),
),
),
child: Text('To Second Screen'),
),
),
);
}
}

Navigator in our app. You can see that in the above code, In builder we are simply returning MyHomePage().
Navigator and pass the routing information accordingly.MaterialApp(
builder: (context, child) {
return Navigator(
// If you don't know about `initialRoute` and `onGenerateRoute`, I've explained these properties below.
initialRoute: "/",
onGenerateRoute: (settings) {
if (settings.name == '/') {
return MaterialPageRoute(builder: (_) => MyHomePage());
}
return null; // Let `onUnknownRoute` handle this behavior.
},
);
});

showDialog and showMenu, and widgets such as Tooltip, PopupMenuButton, also require a Navigator to properly function.routes :namedRoutes, you have to first define all the routes in the application's top-level routing table. i.e, in MaterialApp's routes property.routes as a table where each screen is binded with a particular path. For example, "/home" is binded with HomeScreen() widget.Map<String, Widget Function(BuildContext)> as an input. Where key is the actual pathName (ex: "/home","/signIn" ,etc), and value is actual Widget/Screen (ex: HomeScreen(), SignIn(), etc).MaterialApp(
routes: {
"/": (_)=> MyHomePage(),
"/secondScreen": (_) => MySecondPage(),
},
);
Navigator.pushNamed(context, "/secondScreen"); for navigation.class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () => Navigator.pushNamed(context, "/secondScreen"),
child: Text('To Second Screen'),
),
),
);
}
}

Notice that I've not defined
homeproperty insideMaterialApp. As I've already defined/key in theroutesproperty. TheMaterialAppwill automatically consider/key defined in theroutesmap as aStarting Point of Application. This is not any kind of magic. Behind the scene,Navigator.defaultRouteNamehas/value by default.
home is specified, then it implies an entry in this table for the Navigator.defaultRouteName route /. Note: You cannot specify
homeand/key inrouteboth at the same time. It will lead to an error.
onGenerateRoute :named route.null, For example : MaterialApp(
onGenerateRoute: (settings) {
return null;
},
home: MyHomePage(),
);
discarded and Navigator.defaultRouteName is used instead (/). Which here is MyHomePage().onGenerateRoute.MaterialApp(
onGenerateRoute: (settings) {
if (settings.name == "/secondScreen") {
return MaterialPageRoute(builder: (_) => MySecondPage());
}
},
home: MyHomePage(),
);
parameter named settings , passed in the onGenerateRoute. This settings is called RouteSettings, which provides us two things. name and arguments.name is the name of a routename. For example: If we call Navigator.pushNamed(context, "/secondScreen");, then name gets a value as /secondScreen.arguments is the data which has been passed through the screen. For example:ElevatedButton(
onPressed: () => Navigator.pushNamed(context, '/secondScreen',
arguments: 42), // Passing argument
child: Text('Go to BarPage'),
),
argument property defined in pushNamed constructor, which later will be assigned to the settings.argumentsNavigator.pushNamed(context, "/secondScreen"); for navigation.class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () => Navigator.pushNamed(context, "/secondScreen"),
child: Text('To Second Screen'),
),
),
);
}
}

difference between routes and onGenerateRoute. Both are doing the same thing, right?. Well YES. Both are used when app navigate via a namedRoute.routes is static. It means it doesn't offer a functionality of passing arguments between screen, or implementing different PageRoute.onGenerateRoute property comes into the picture. onGenerateRoute, you can pass arguments between routes. Which is not possible in routes.MaterialApp(
routes: {
'/': (_) => HomePage(),
'/secondScreen': (_) => SecondPage(),
},
onGenerateRoute: (settings) {
if (settings.name == '/thirdScreen') {
final value = settings.arguments as int; // Retrieve the value.
return MaterialPageRoute(
builder: (_) => ThirdPage(value)); // Passing the value
}
return null;
},
),
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('HomePage')),
body: Center(
child: Column(
children: [
ElevatedButton(
onPressed: () => Navigator.pushNamed(context, '/secondScreen'),
child: Text('Go to Second Page'),
),
SizedBox(height:10.0),
ElevatedButton(
onPressed: () => Navigator.pushNamed(context, '/thirdScreen',
arguments: 123),
child: Text('Go to Third'),
),
],
),
),
);
}
}
class SecondPage extends StatelessWidget {
@override
Widget build(_) => Scaffold(
appBar: AppBar(
title: Text('SecondPage'),
),
);
}
class ThirdPage extends StatelessWidget {
final int value;
ThirdPage(this.value);
@override
Widget build(_) => Scaffold(
appBar: AppBar(
title: Text('ThirdPage, value = $value'),
),
);
}

onGenerateInitialRoutes:initialRoute is provided.IntroPage if he/she is not authorized and to HomePage if authorized.MaterialApp(
onGenerateInitialRoutes: (route) {
if (isAuthorized) {
return <Route>[
MaterialPageRoute(builder: (context) => HomePage())
];
} else {
return <Route>[
MaterialPageRoute(builder: (context) => IntroPage())
];
}
},
onGenerateRoute: (settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (_) => IntroPage());
case '/homePage':
return MaterialPageRoute(builder: (_) => HomePage());
}
},
),
onUnknownRoute :onGenerateRoute fails to generate a route.MaterialApp(
onUnknownRoute: (RouteSettings settings) {
return MaterialPageRoute<void>(
settings: settings,
builder: (BuildContext context) =>
Scaffold(body: Center(child: Text('Not Found'))),
);
},
home: HomePage(),
),
darkTheme :ThemeData in the darkTheme property, we are telling our app to use this particular ThemeData when the system requests for DarkTheme.LightMode and DarkMode. Whenever user toggles the theme to DarkTheme, entire app will use the ThemeData that is specified in the darkTheme property of MaterialApp.MaterialApp(
darkTheme: ThemeData(
brightness: Brightness.dark
),
home: HomePage(),
),
);
}

ThemeDatas' primaryColor when the app is in dark modeMaterialApp(
darkTheme: ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.red
),
home: HomePage(),
),

primaryColor applied successfully.theme:default theme that will be applied to our app. This theme will be applied when the themMode value is light. i.e. ThemeMode.lightthemeMode is ThemeMode.dark, you have to specify ThemeData in darkMode property as discussed above.primaryColor, secondaryColor, buttonColor ,etc of our app.ThemeData as an input.MaterialApp(
themeMode: ThemeMode.light,
theme: ThemeData(
brightness: Brightness.light,
primaryColor: Colors.green
),
home: HomePage(),
),

themeMode:theme and darkTheme are provided.default value of themeMode is ThemeMode.system, which means whatever the theme of the system will be applied by default by our app.ThemeMode has 3 enums. ThemeMode.dark: Use the theme defined in darkTheme property. It will always use the dark mode (if available) regardless of system preference.ThemeMode.light: Use the theme defined in theme property. It will always use the light mode regardless of system preference.ThemeMode.system: Use either the light or dark theme based on what the user has selected in the system settings.MaterialApp(
themeMode: ThemeMode.dark,
theme: ThemeData(
brightness: Brightness.light,
primaryColor: Colors.green
),
darkTheme: ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.red
),
home: HomePage(),
),
themeMode is ThemeMode.dark. Because of that, the darkTheme will be applied to our app. If the value is ThemeMode.light then theme will be applied to our app.darkMode and lightMode by toggling the value of themeMode using some kind of listener that will listen to the toggle event and toggles the themeMode values accordingly as shown below.
highContrastDarkTheme:highContrastDarkTheme will be applied.MediaQueryData.highContrast boolean flag.ThemeData.brightness set to Brightness.dark.darkTheme when null.highContrastTheme:high contrast is requested by the system we can use thethemedefined inhighContrastTheme`.theme when null.initialRoute:initialRoute property tells our app which is the initial page/widget to load.String. And default to dart:ui.PlatformDispatcher.defaultRouteName. Which we can override too.MaterialApp(
initialRoute: "/",
routes: {
'/': (_) => HomePage(),
},
),
HomePage as initial route as the initialRoute is /.initialRoute , home, onGenerateRoute, and onGenerateInitialRoute.

homes' way to render initial widget:MaterialApp(
home: HomePage(),
),
initialRoutes' way to render initial widget:MaterialApp(
initialRoute: '/',
routes: {
'/': (_) => HomePage(),
},
),
onGenerateRoutes' way to render initial widget :MaterialApp(
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name == '/') return MaterialPageRoute(builder: (_) => HomePage());
return MaterialPageRoute(builder: (_) => UnknownPage());
},
),
onGenerateInitialRoutes' way to render initial widget :MaterialApp(
onGenerateInitialRoutes: (route) {
return [
MaterialPageRoute(builder: (_) => HomePage())
];
}
),
navigatorKey:navigation we need BuildContext. Without context we can't navigate to other screens.model class? Is there any way to navigate without using BuildContext?
GlobalKeys can be used to access the state of a StatefulWidget and that's what we'll use to access the NavigatorState outside of the build context.NavigationService class that contains the global key, we'll set that key on initialization and we'll expose a function on the service to navigate given a name.class NavigationService {
final GlobalKey<NavigatorState> navigatorKey =
new GlobalKey<NavigatorState>();
Future<dynamic> navigateTo(String routeName) {
return navigatorKey.currentState.pushNamed(routeName);
}
}
void setupLocator() {
locator.registerLazySingleton(() => NavigationService());
}
GlobalKey as the NavigatorKey to our MaterialApp.MaterialApp(
navigatorKey: locator<NavigationService>().navigatorKey,
onGenerateRoute: (routeSettings) {
switch (routeSettings.name) {
case 'secondPage':
return MaterialPageRoute(builder: (context) => SecondPage());
}
},
home: HomePage()
);
navigateTo function by passing pathName . locator<NavigationService>().navigateTo('SecondPage');
navigatorObserver :Navigator and is also responsible for screen transitions. There are different options like push, pop screens.NavigatorObserver can also be passed to Navigator to receive events related to screen-transitions.NavigatorObserver can also be used but if the handling of it in the state is required then it is a better option to go with the RouteObserver.
RouteObserverinforms subscribers whenever a route of typeRis pushed on top of their own route of typeRor popped from it. This is for example useful to keep track of page transitions, e.g. aRouteObserver<PageRoute>will inform subscribedRouteAwareswhenever the user navigates away from the current page route to another page route.
RouteObserver for using 3 methods, didPush(), didReplace(), didPop(), class MyRouteObserver extends RouteObserver<PageRoute<dynamic>> {
void _sendScreenView(PageRoute<dynamic> route) {
var screenName = route.settings.name;
print('screenName $screenName');
// do something with it, ie. send it to your analytics service collector
}
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
super.didPop(route, previousRoute);
if (previousRoute is PageRoute && route is PageRoute) {
_sendScreenView(previousRoute);
}
}
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
super.didPush(route, previousRoute);
if (route is PageRoute) {
_sendScreenView(route);
}
}
} // End of MyRouteObserver class
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorObservers: [MyRouteObserver()],
routes: {
'pageone': (context) => PageOne(),
'pagetwo': (context) => PageTwo()
},
home: MyHomePage(),
);
}
}
locale :MaterialApp class allows us to immediately specify what locale we want our app to use.null then the system's locale will be applied to our app.locale property allows us to force the locale of the app to the locale specified in locale, regardless of the locale of the device.Locale(String _languageCode, [String? _countryCode]) as an input.MaterialApp(
locale: Locale('hi', ''),
home: HomeScreen()
);
locale of our app.localeResolutionCallback :locale when the app is started, and when the user changes the device's locale.localeListResolutionCallback instead of a localeResolutionCallback when possible, as localeListResolutionCallback is in the first priority.MaterialApp(
localeResolutionCallback: (deviceLocale, supportedLocales) {
for (var locale in supportedLocales) {
if (locale.languageCode == deviceLocale!.languageCode &&
locale.countryCode == deviceLocale.countryCode) {
return deviceLocale;
}
}
return supportedLocales.first;
},
home: HomePage(),
),
locale from the supportedLocale.localizationsDelegates :material and cupertino widget, For ex: calender, datePicker etc, there are obviously texts/numbers written on it.
localizationsDelegates provides us three important in-built delegates:
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate.delegates are responsible for translating those material and cupertino widgets. 
delegates to translate our app's texts. I can't explain that here, as it is out of the scope of this blog. I'll explain this in a future blog.localeListResolutionCallback :locale when the app is started, and when the user changes the device's locale.localeListResolutionCallback is provided, Flutter will first attempt to resolve the locale with the provided localeListResolutionCallback. If the callback or result is null, it will fallback to trying the localeResolutionCallback. If both localeResolutionCallback and localeListResolutionCallback are left null or fail to resolve (return null), the a basic fallback algorithm will be used.priority of each available fallback is:localeListResolutionCallback is attempted first.localeResolutionCallback is attempted second.supportedLocales, is attempted last.locale: List of locales.supportedLocale: supportedLocaleMaterialApp(
localeListResolutionCallback: (locales, supportedLocales) {
print(locales);
print(supportedLocales);
return null;
},
home: HomePage(),
),

dartad.dev (Windows).restorationScopeId :State Preservation and Restoration concepts are used. It ensures that the app returns to its previous state when it launches again.RestorationManager which is responsible for handling all the state restoration work. We don't usually use it directly.RestorationBucket is used to hold the piece of the restoration data that our app needs to restore its state later.RestorationScope is used to provide a scoped RestorationBucket to its descendants.restorationScopeId parameter is null then, the restoration is disabled for its descendants.RestorationMixin is the one that is used by our widget's state. It provides use an API to save and restore our state.restorable properties, which are used to represent the data to be stored in the buckets.
restorationScopeId to our MaterialApp.MaterialApp(
restorationScopeId: 'root', //default value if null.
home: HomePage(),
);
RestorationMixin mixed-in with HomePageclass _HomePageState extends State<HomePage> with RestorationMixin {
// .....
}
restorable properties that we want to restore if something went wrong.final RestorableInt _index = RestorableInt(0);
@override
// The restoration bucket id for current page
String get restorationId => 'home_page';
@override
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
// Register our property to be saved every time it changes,
// and to be restored every time our app is killed by the OS!
registerForRestoration(_index, 'nav_bar_index');
}
Don't keep activities from the mobile's Developer Options.
restoration you will notice that the index will always come back to Home.
shortcuts:shortcut property.Map of type LogicalKeyState.LogicalKeyState is a set of LogicalKeyboardKeys that can be used as the keys in a map.class AddIntent extends Intent {}
MaterialApp(
shortcuts: {
LogicalKeySet(LogicalKeyboardKey.arrowUp): AddIntent(),
},
home: MyHomePage(),
);
Actions. This will dispatch the actions when you press the shortcut key provided in shortcut property.class _MyHomePageState extends State<MyHomePage> {
int _number = 0;
changeNumber() {
setState((){
_number += 1;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Actions(
actions: {
AddIntent: CallbackAction<AddIntent>(
onInvoke: (intent) => changeNumber(),
),
},
child: Center(
child: Container(
height:100,
width:100,
color:Colors.red,
child: Focus(
autofocus: true,
child: Center(
child: Text("$_number")
),
)
),
)
),
);
}
}

scaffoldMessengerKey :MaterialApp widget.
Previous Widget In Detail : AlertDialog