Flutter Widget In Detail: MaterialApp

Flutter Widget In Detail: MaterialApp

Detailed Explanation of MaterialApp Widget

Introduction:

  • MaterialApp is Futter's one of the most powerful widgets. if you create a basic Flutter app then the first widget you'll see is MaterialApp
  • MaterialApp wraps a number of widgets that are commonly required for material design applications.
  • By wrapping your application inside the MaterialApp, you're telling your app to use Android's Material Design, which is a design system created by Google to help teams build high-quality digital experiences for Android, iOS, Flutter, and the web.
  • material-design.jpg
  • More about Material-Design
  • But if you want to follow iOS design patterns, then you have to wrap your app inside CupertinoApp. There are many widgets provided by flutter to design your app for iOS platform.
  • cup-final.png
  • Another thing I want to point out is that MaterialApp and CupertinoApp are built upon WidgetApp.

  • Let's understand the MaterialApp widget and its properties in detail with some examples.

MaterialApp

  • We can consider this as an application that uses material design.
  • Before creating MaterialApp we have to import material package which is provided by flutter SDK.
  • To import material package :
  • import 'package:flutter/material.dart';
    
  • This package provides us all the widgets that we can use in our application. For example: AppBar, Scaffold, BottomNavigationBar, Card, Chip, BottomSheet, etc.
  • MaterialApp must have at least one of 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();
         }
    }
    
  • Output :
  • errormaterial.png
  • Now let's take a deep dive into all the properties, and understand what each and every property does.

  • home:

  • This is a default route of an app.
  • It means whatever is defined here is the first thing you will see on the screen.
  • It takes Widget as an input.
  • Usually, we define home, signIn, signUp, splash screens, but you can put any widget here.
  • MaterialApp(
        home: MyFirstPage(),
    );
    
  • Output :
  • home.png

  • title:

  • This takes String as value.
  • If you put value in title, you will not see any changes in your app. It will still show an empty blank screen.
  • You will see this title when you press the "recent apps" button.
  • Let's define title in MaterialApp
  • MaterialApp(
       title: "Widget In Detail",
       home: MyFirstPage(),
    );
    

  • debugShowCheckedModeBanner:

  • This is a banner that indicates that currently, our app is running in `debug mode.
  • The default value of this property is true.
  • To remove this banner, simply put false inside it.
  • In release mode, this has no effect.
  • MaterialApp(
        debugShowCheckedModeBanner: true,
        title: "Widget In Detail",
        home: MyFirstPage(),
    );
    
  • Output :
  • Debugbanner.png

  • builder :

  • A builder that builds a widget given to a child.
  • builder function takes two parameter context and widget.
  • The return type of builder is Widget.
    MaterialApp(
          builder: (context,widget) {
              return widget;
            }
    );
    
  • By using builder property, we can override properties like Navigator, MediaQuery, or internationalization that is set by MaterialApp
  • For example, If no routes are provided to the regular MaterialApp 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.
  • If builder is null, routes must be provided using one of the other properties (home, routes, onGenerateRoute, or onUnknownRoute,).

    Use cases :

    1. To insert widgets above the Navigator.
    2. To insert widgets above the Router but below the other widgets created by the WidgetsApp widget
    3. For replacing the Navigator/Router entirely.
  • If Navigator is not provided in the builder we will not able to use Navigator.push, Navigator.pop, Hero etc.
  • TheNightmareGIF.gif
  • Okay let me take an example. Consider the below code :
  • 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'),
          ),
        ),
      );
    }
    }
    
  • builder1.png
  • Now if we press the button, nothing will happen. Why? Because we haven't passed Navigator in our app. You can see that in the above code, In builder we are simply returning MyHomePage().
  • btnnotworking.gif
  • Let's wrap that child inside 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.
        },
        );
    });
    
  • Output :
  • btnworking.gif
  • So as you can see, now we are successfully navigating to Second Screen.
  • Material-specific features such as showDialog and showMenu, and widgets such as Tooltip, PopupMenuButton, also require a Navigator to properly function.

routes :

  • If you want to navigate via namedRoutes, you have to first define all the routes in the application's top-level routing table. i.e, in MaterialApp's routes property.
  • You can think of routes as a table where each screen is binded with a particular path. For example, "/home" is binded with HomeScreen() widget.
  • It takes 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).
  • Example :
    MaterialApp(
        routes: {
          "/": (_)=> MyHomePage(),
          "/secondScreen": (_) => MySecondPage(),
        },
      );
    
  • Now you can use 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'),
          ),
        ),
      );
    }
    }
    
  • Output :
  • btnworking.gif
  • Notice that I've not defined home property inside MaterialApp. As I've already defined / key in the routes property. The MaterialApp will automatically consider / key defined in the routes map as a Starting Point of Application. This is not any kind of magic. Behind the scene, Navigator.defaultRouteName has / value by default.

  • If home is specified, then it implies an entry in this table for the Navigator.defaultRouteName route /.

    Note: You cannot specify home and / key in route both at the same time. It will lead to an error.


onGenerateRoute :

  • This is used when the app navigates to the named route.
  • If this returns null, For example :
    MaterialApp(
        onGenerateRoute: (settings) {
          return null;
        },
        home: MyHomePage(),
    );
    
  • Then all the routes are discarded and Navigator.defaultRouteName is used instead (/). Which here is MyHomePage().
  • Let's see how we can generate route using onGenerateRoute.
  • Example :
  • MaterialApp(
        onGenerateRoute: (settings) {
          if (settings.name == "/secondScreen") {
            return MaterialPageRoute(builder: (_) => MySecondPage());
          }
        },
        home: MyHomePage(),
    );
    
  • As you can see in the above snippet, there is one 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'),
    ),
    
  • Here as you can see the argument property defined in pushNamed constructor, which later will be assigned to the settings.arguments
  • Now you can use 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'),
          ),
        ),
      );
    }
    }
    
  • TheFreshPrinceOfBelAirWaitAMinuteGIF.gif
  • Then what's the difference between routes and onGenerateRoute. Both are doing the same thing, right?. Well YES. Both are used when app navigate via a namedRoute.
  • BUT, Both has its different use cases. Let's understand..
  • routes is static. It means it doesn't offer a functionality of passing arguments between screen, or implementing different PageRoute.
  • This is why onGenerateRoute property comes into the picture.
  • With onGenerateRoute, you can pass arguments between routes. Which is not possible in routes.
  • Example
  • 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'),
          ),
        );
    }
    
  • Output :
  • onGenerate.gif

onGenerateInitialRoutes:

  • The routes generator callback used for generating initial routes if initialRoute is provided.
  • One use case can be , When you want to navigate user to IntroPage if he/she is not authorized and to HomePage if authorized.
  • Example :
  • 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 :

  • This will return a route when onGenerateRoute fails to generate a route.
  • This callback is typically used for error handling. For example, this callback might always generate a "not found" page that describes the route that wasn't found.
  • Example :
  • MaterialApp(
        onUnknownRoute: (RouteSettings settings) {
          return MaterialPageRoute<void>(
            settings: settings,
            builder: (BuildContext context) =>
                Scaffold(body: Center(child: Text('Not Found'))),
          );
        },
        home: HomePage(),
      ),
    

darkTheme :

  • By applying the ThemeData in the darkTheme property, we are telling our app to use this particular ThemeData when the system requests for DarkTheme.
  • For example : We have an app where we've provided toggle for 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.
  • Example :
    MaterialApp(
        darkTheme: ThemeData(
          brightness: Brightness.dark
        ),
      home: HomePage(),
      ),
    );
    }
    
  • Output :
  • darkTheme2.png
  • Let's tweak the values of ThemeDatas' primaryColor when the app is in dark mode
  • MaterialApp(
        darkTheme: ThemeData(
          brightness: Brightness.dark,
          primaryColor: Colors.red
        ),
        home: HomePage(),
    ),
    
  • Output :
  • darkTheme3.png
  • As we can see, the primaryColor applied successfully.

theme:

  • This is a default theme that will be applied to our app. This theme will be applied when the themMode value is light. i.e. ThemeMode.light
  • If you want to edit the theme of an app when themeMode is ThemeMode.dark, you have to specify ThemeData in darkMode property as discussed above.
  • Here we can define default primaryColor, secondaryColor, buttonColor ,etc of our app.
  • It takes ThemeData as an input.
  • Example :
    MaterialApp(
        themeMode: ThemeMode.light,
        theme: ThemeData(
          brightness: Brightness.light,
          primaryColor: Colors.green
        ),
        home: HomePage(),
    ),
    
  • Output :
  • defaultTheme.png

themeMode:

  • This property determines which theme will be used by the application if both theme and darkTheme are provided.
  • The 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.
  • Example :
    MaterialApp(
        themeMode: ThemeMode.dark,
        theme: ThemeData(
          brightness: Brightness.light,
          primaryColor: Colors.green
        ),
        darkTheme: ThemeData(
          brightness: Brightness.dark,
          primaryColor: Colors.red
        ),
        home: HomePage(),
    ),
    
  • As shown in the above example, the value of 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.
  • You can switch between 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.
  • toggleTheme.gif

highContrastDarkTheme:

  • When a 'dark mode' and 'high contrast' is requested by the system the theme defined in highContrastDarkTheme will be applied.
  • Some host platforms (for example, iOS) allow the users to increase contrast through an accessibility setting.
  • You can check whether the user requested a high contrast between foreground and background content by MediaQueryData.highContrast boolean flag.
  • This theme should have a ThemeData.brightness set to Brightness.dark.
  • It will use darkTheme when null.

highContrastTheme:

  • When high contrast is requested by the system we can use thethemedefined inhighContrastTheme`.
  • It will use the theme when null.

initialRoute:

  • The initialRoute property tells our app which is the initial page/widget to load.
  • The value is a type of String. And default to dart:ui.PlatformDispatcher.defaultRouteName. Which we can override too.
  • Example :
    MaterialApp(
        initialRoute: "/",
        routes: {
          '/': (_) => HomePage(),
        },
    ),
    
  • Here the app will consider HomePage as initial route as the initialRoute is /.
  • You might think what is the difference between initialRoute , home, onGenerateRoute, and onGenerateInitialRoute. ExplainTheDifferenceClayJensenGIF.gif
  • There is the only a difference in code readability (but not limited to), see all of them are doing the same job but in different ways:
  • 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())
        ];
      }
    ),
    

  • As we've seen above, we are writing our navigation business logic directly from our UI(in view (if we consider MVC)) page. And that's how we usually do. Because for navigation we need BuildContext. Without context we can't navigate to other screens.
  • So Is there any way to write our business logic inside the model class? Is there any way to navigate without using BuildContext?
  • YeahMuitoBemGIF.gif
  • YES. In Flutter 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.
  • We can create 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);
    }
    }
    
  • Then we register out NavigationService with the locator (here get_it is used for registering).
  • void setupLocator() {
    locator.registerLazySingleton(() => NavigationService());
    }
    
  • In the main file, we then pass our 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()
    );
    
  • Now we can navigate by calling the navigateTo function by passing pathName .
  • locator<NavigationService>().navigateTo('SecondPage');
    

  • As you know in the flutter navigation is handled by Navigator and is also responsible for screen transitions. There are different options like push, pop screens.
  • The list of NavigatorObserver can also be passed to Navigator to receive events related to screen-transitions.
  • A custom 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.
  • What is RouterObserver?

    RouteObserver informs subscribers whenever a route of type R is pushed on top of their own route of type R or popped from it. This is for example useful to keep track of page transitions, e.g. a RouteObserver<PageRoute> will inform subscribed RouteAwares whenever the user navigates away from the current page route to another page route.

  • Let's understand how to use RouteObserver in our app,
  • We have to extend 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
    
  • After that, You need to call this class in main.dart & It will automatically notify all the screen transitions.
  • 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 :

  • This property of the MaterialApp class allows us to immediately specify what locale we want our app to use.
  • If the value of this is null then the system's locale will be applied to our app.
  • This locale property allows us to force the locale of the app to the locale specified in locale, regardless of the locale of the device.
  • It takes Locale(String _languageCode, [String? _countryCode]) as an input.
  • Example :
    MaterialApp(
    locale: Locale('hi', ''),
    home: HomeScreen()
    );
    
  • This is the easiest way to define locale of our app.

localeResolutionCallback :

  • This callback is responsible for choosing the app's locale when the app is started, and when the user changes the device's locale.
  • It is recommended to provide a localeListResolutionCallback instead of a localeResolutionCallback when possible, as localeListResolutionCallback is in the first priority.
  • Example :
    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(),
      ),
    
  • What this above code will do is, It will check if the current app supports the device locale or not. If not then, we can simply return the locale from the supportedLocale.

localizationsDelegates :

  • If we see the material and cupertino widget, For ex: calender, datePicker etc, there are obviously texts/numbers written on it.
  • datepicker.jpg
  • Now what if we want to translate those texts?
  • So this localizationsDelegates provides us three important in-built delegates: GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate.
  • These three delegates are responsible for translating those material and cupertino widgets.
  • datepickerar.png
  • We can also create our own 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 :

  • This callback is responsible for choosing the app's locale when the app is started, and when the user changes the device's locale.
  • When a 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.
  • The priority of each available fallback is:
  • localeListResolutionCallback is attempted first.
  • localeResolutionCallback is attempted second.
  • Flutter's basic resolution algorithm, as described in supportedLocales, is attempted last.
  • This callback function takes two arguments.
  • locale: List of locales.
  • supportedLocale: supportedLocale
  • Example :
    MaterialApp(
        localeListResolutionCallback: (locales, supportedLocales) {
          print(locales);
          print(supportedLocales);
          return null;
        },
        home: HomePage(),
    ),
    
  • Output :
  • localeList.png
  • I'm executing this code on dartad.dev (Windows).

restorationScopeId :

  • As a developer, we need to take care of the app's user interface by preserving it. By doing this, It creates an illusion that your app is always running.
  • Sometimes interruptions can occur on devices and might cause the system to terminate your app to free up resources.
  • But the users do not know all these behind the scene activities. They only expect your app to be in the same state as when they left.
  • For that State Preservation and Restoration concepts are used. It ensures that the app returns to its previous state when it launches again.
  • Flutter has the 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.
  • If the 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.
  • And finally, we have to use restorable properties, which are used to represent the data to be stored in the buckets.
  • LetsTakeAnExampleForInstanceGIF.gif
  • First of all we have to provide a restorationScopeId to our MaterialApp.
  • MaterialApp(
        restorationScopeId: 'root',  //default value if null.
        home: HomePage(),
    );
    
  • Then use RestorationMixin mixed-in with HomePage
  • class _HomePageState extends State<HomePage> with RestorationMixin {
      // .....
    }
    
  • After that create restorable properties that we want to restore if something went wrong.
  • final RestorableInt _index = RestorableInt(0);
    
  • The final step is to resgister our restorable properties for restoration.
  • @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');
    }
    
  • If you want to test this code. First enable the Don't keep activities from the mobile's Developer Options.
  • Now lets see the output :
  • restoration1.gif
  • As you can see that the selected setting option is still selected.
  • But if you try this without restoration you will notice that the index will always come back to Home.
  • restoration2.gif

shortcuts:

  • We can add shortcut keys to perform certain tasks by using the shortcut property.
  • It takes Map of type LogicalKeyState.
  • LogicalKeyState is a set of LogicalKeyboardKeys that can be used as the keys in a map.
  • Example :
    class AddIntent extends Intent {}
    
    MaterialApp(
        shortcuts: {
          LogicalKeySet(LogicalKeyboardKey.arrowUp): AddIntent(),
        },
        home: MyHomePage(),
    );
    
  • Now we have to wrap the widget tree inside 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")
            ),
            )
          ),
          )
        ),
      );
    }
    }
    
  • Output :
  • shortcut.gif

scaffoldMessengerKey :

  • A key to use when building the ScaffoldMessenger.
  • If a scaffoldMessengerKey is specified, the ScaffoldMessenger can be directly manipulated without first obtaining it from a BuildContext via ScaffoldMessenger.of: from the scaffoldMessengerKey, use the GlobalKey.currentState getter.

THAT'S IT

  • That's all you need to know about the MaterialApp widget.
  • I know that, it's a lot of properties and a lot of stuff is going on. But if you try and practice it, you will remember it easily.

PeaceOutImOutGIF.gif

Previous Widget In Detail : AlertDialog

Did you find this article valuable?

Support Dhruv Nakum by becoming a sponsor. Any amount is appreciated!