• 18-19 College Green, Dublin 2
  • 01 685 9088
  • info@cunninghamwebsolutions.com
  • cunninghamwebsolutions
    Cunningham Web Solutions
    • Home
    • About Us
    • Our Services
      • Web Design
      • Digital Marketing
      • SEO Services
      • E-commerce Websites
      • Website Redevelopment
      • Social Media Services
    • Digital Marketing
      • Adwords
      • Social Media Services
      • Email Marketing
      • Display Advertising
      • Remarketing
    • Portfolio
    • FAQ’s
    • Blog
    • Contact Us
    MENU CLOSE back  

    Responsive Web And Desktop Development With Flutter

    You are here:
    1. Home
    2. Web Design
    3. Responsive Web And Desktop Development With Flutter
    Thumbnail for 24835

    Responsive Web And Desktop Development With Flutter

    Responsive Web And Desktop Development With Flutter

    Carmine Zaccagnino

    2020-04-22T10:00:00+00:00
    2020-04-22T12:36:02+00:00

    This tutorial is not an introduction to Flutter itself. There are plenty of articles, videos and several books available online with simple introductions that will help you learn the basics of Flutter. Instead, we’ll be covering the following two objectives:

    1. The current state of Flutter non-mobile development and how you can run Flutter code in the browser, on a desktop or laptop computer;
    2. How to create responsive apps using Flutter, so that you can see its power — especially as a web framework — on full display, ending with a note about routing based on URL.

    Let’s get into it!

    What Is Flutter, Why It’s Important, What It Has Evolved Into, Where It’s Going

    Flutter is Google’s latest app development framework. Google envisions it to be all-encompassing: It will enable the same code to be executed on smartphones of all brands, on tablets, and on desktop and laptops computer as native apps or as web pages.

    It’s a very ambitious project, but Google has been incredibly successful until now particularly in two aspects: in creating a truly platform-independent framework for Android and iOS native apps that works great and is fully ready for production use, and in creating an impressive front-end web framework that can share 100% of the code with a compatible Flutter app.

    In the next section, we’re going to see what makes the app compatible and what’s the state of non-mobile Flutter development as of now.

    Non-Mobile Development With Flutter

    Non-mobile development with Flutter was first publicized in a significant way at Google I/O 2019. This section is about how to make it work and about when it works.

    How To Enable Web And Desktop Development

    To enable web development, you must first be on Flutter’s beta channel. There are two ways to get to that point:

    • Install Flutter directly on the beta channel by downloading the appropriate latest beta version from the SDK archive.
    • If you already have Flutter installed, switch to the beta channel with $ flutter channel beta, and then perform the switch itself by updating your Flutter version (which is actually a git pull on the Flutter installation folder) with $ flutter upgrade.

    After that, you can run this:

    $ flutter config --enable-web

    Desktop support is much more experimental, especially due to a lack of tooling for Linux and Windows, making plugin development especially a major pain, and due to the fact that the APIs used for it are intended for proof-of-concept use and not for production. This is unlike web development, which is using the tried-and-tested dart2js compiler for release builds, which are not even supported for Windows and Linux native desktop apps.

    Note: Support for macOS is slightly better than support for Windows and Linux, but it still isn’t as good as support for the web and not nearly as good as the full support for mobile platforms.

    To enable support for desktop development, you need to switch to the master release channel by following the same steps outlined earlier for the beta channel. Then, run the following by replacing with either linux, windows, or macos:

    $ flutter config --enable-<OS_NAME>-desktop

    At this point, if you have issues with any of the following steps that I’ll be describing because the Flutter tool isn’t doing what I’m saying it should do, some common troubleshooting steps are these:

    • Run flutter doctor to check for issues. A side effect of this Flutter command is that it should download any tools it needs that it doesn’t have.
    • Run flutter upgrade.
    • Turn it off and on again. The old tier-1 technical-support answer of restarting your computer might be just what is needed for you to be able to enjoy the full riches of Flutter.

    Running And Building Flutter Web Apps

    Flutter web support isn’t bad at all, and this is reflected in the ease of development for the web.

    Running this…

    $ flutter devices

    … should show right away an entry for something like this:

    Web Server • web-server • web-javascript • Flutter Tools

    Additionally, running the Chrome browser should cause Flutter to show an entry for it as well. Running flutter run on a compatible Flutter project (more on that later) when the only “connected device” showing up is the web server will cause Flutter to start a web server on localhost:, which will allow you to access your Flutter web app from any browser.

    If you have installed Chrome but it’s not showing up, you need to set the CHROME_EXECUTABLE environment variable to the path to the Chrome executable file.

    Running And Building Flutter Desktop Apps

    After you’ve enabled Flutter desktop support, you can run a Flutter app natively on your development workstation with flutter run -d , replacing with the same value you used when enabling desktop support. You can also build binaries in the build directory with flutter build .

    Before you can do any of that, though, you need to have a directory containing what Flutter needs to build for your platform. This will be created automatically when you create a new project, but you’ll need to create it for an existing project with flutter create .. Also, the Linux and Windows APIs are unstable, so you might have to regenerate them for those platforms if the app stops working after a Flutter update.

    When Is An App Compatible?

    What have I meant all along when mentioning that a Flutter app has to be a “compatible project” in order for it to work on desktop or the web? Put simply, I mean that it mustn’t use any plugin that doesn’t have a platform-specific implementation for the platform on which you’re trying to build.

    To make this point absolutely clear to everyone and avoid misunderstanding, please note that a Flutter plugin is a particular Flutter package that contains platform-specific code that is necessary for it to provide its features.

    For example, you can use the Google-developed url_launcher package as much as you want (and you might want to, given that the web is built on hyperlinks).

    An example of a Google-developed package the usage of which would preclude web development is path_provider, which is used to get the local storage path to save files to. This is an example of a package that, incidentally, isn’t of any use to a web app, so not being able to use it isn’t really a bummer, except for the fact that you need to change your code in order for it to work on the web if you’re using it.

    For example, you can use the shared_preferences package, which relies on HTML localStorage on the web.

    Similar caveats are valid regarding desktop platforms: Very few plugins are compatible with desktop platforms, and, as this is a recurring theme, much more work on this needs to be done on the desktop side than is really necessary on Flutter for the web.

    Creating Responsive Layouts In Flutter

    Because of what I’ve described above and for simplicity, I’m going to assume for the rest of this post that your target platform is the web, but the basic concepts apply to desktop development as well.

    Supporting the web has benefits and responsibilities. Being pretty much forced to support different screen sizes might sound like a drawback, but consider that running the app in the web browsers enables you to see very easily how your app will look on screens of different sizes and aspect ratios, without having to run separate mobile device emulators.

    Now, let’s talk code. How can you make your app responsive?

    There are two perspectives from which this analysis is done:

    1. “What widgets am I using or can I use that can or should adapt to screens of different sizes?”
    2. “How can I get information about the size of the screen, and how can I use it when writing UI code?”

    We’ll answer the first question later. Let’s first talk about the latter, because it can be dealt with very easily and is at the heart of the issue. There are two ways to do this:

    1. One way is to take the information from the MediaQueryData of the MediaQuery root InheritedWidget, which has to exist in the widget tree in order for a Flutter app to work (it’s part of MaterialApp/WidgetsApp/CupertinoApp), which you can get, just like any other InheritedWidget, with MediaQuery.of(context), which has a size property, which is of type Size, and which therefore has two width and height properties of the type double.
    2. The other way is to use a LayoutBuilder, which is a builder widget (just like a StreamBuilder or a FutureBuilder) that passes to the builder function (along with the context) a BoxConstraints object that has minHeight, maxHeight, minWidth and maxWidth properties.

    Here’s an example DartPad using the MediaQuery to get constraints, the code for which is the following:

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(context) =>
        MaterialApp(
          home: MyHomePage()
        );
    }
    
    class MyHomePage extends StatelessWidget {
      @override
      Widget build(context) =>
        Scaffold(
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                Text(
                  "Width: ${MediaQuery.of(context).size.width}",
                  style: Theme.of(context).textTheme.headline4
                ),
                Text(
                  "Height: ${MediaQuery.of(context).size.height}",
                  style: Theme.of(context).textTheme.headline4
                )
              ]
           )
         )
       );
    }

    And here’s one using the LayoutBuilder for the same thing:

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(context) =>
        MaterialApp(
          home: MyHomePage()
        );
    }
    
    class MyHomePage extends StatelessWidget {
      @override
      Widget build(context) =>
        Scaffold(
          body: LayoutBuilder(
            builder: (context, constraints) => Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  Text(
                    "Width: ${constraints.maxWidth}",
                    style: Theme.of(context).textTheme.headline4
                  ),
                  Text(
                    "Height: ${constraints.maxHeight}",
                    style: Theme.of(context).textTheme.headline4
                  )
                ]
             )
           )
         )
      );
    }

    Now, let’s think about what widgets can adapt to the constraints.

    Fist of all, let’s think about the different ways of laying out multiple widgets according to the size of the screen.

    The widget that most easily adapts is the GridView. In fact, a GridView built using the GridView.extent constructor doesn’t even need your involvement to be made responsive, as you can see in this very simple example:

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(context) =>
        MaterialApp(
          home: MyHomePage()
        );
    }
    
    class MyHomePage extends StatelessWidget {
      final List elements = [
        "Zero",
        "One",
        "Two",
        "Three",
        "Four",
        "Five",
        "Six",
        "Seven",
        "Eight",
        "A Million Billion Trillion",
        "A much, much longer text that will still fit"
      ];
    
    
      @override
      Widget build(context) =>
        Scaffold(
          body: GridView.extent(
            maxCrossAxisExtent: 130.0,
            crossAxisSpacing: 20.0,
            mainAxisSpacing: 20.0,
            children: elements.map((el) => Card(child: Center(child: Padding(padding: EdgeInsets.all(8.0), child: Text(el))))).toList()
          )
       );
    }

    You can accommodate content of different sizes by changing the maxCrossAxisExtent.

    That example mostly served the purpose of showing the existence of the GridView.extent GridView constructor, but a much smarter way to do that would be to use a GridView.builder with a SliverGridDelegateWithMaxCrossAxisExtent, in this case where the widgets to be shown in the grid are dynamically created from another data structure, as you can see in this example:

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(context) =>
        MaterialApp(
          home: MyHomePage()
        );
    }
    
    class MyHomePage extends StatelessWidget {
      final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"];
    
    
      @override
      Widget build(context) =>
        Scaffold(
          body: GridView.builder(
            itemCount: elements.length,
            gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
              maxCrossAxisExtent: 130.0,
              crossAxisSpacing: 20.0,
              mainAxisSpacing: 20.0,
            ),
            itemBuilder: (context, i) => Card(
              child: Center(
                child: Padding(
                  padding: EdgeInsets.all(8.0), child: Text(elements[i])
                )
              )
            )
          )
       );
    }

    An example of GridView adapting to different screens is my personal landing page, which is a very simple Flutter web app consisting of a GridView with a bunch of Cards, just like that previous example code, except that the Cards are a little more complex and larger.

    A very simple change that could be made to apps designed for phones would be to replace a Drawer with a permanent menu on the left when there is space.

    For example, we could have a ListView of widgets, like the following, which is used for navigation:

    class Menu extends StatelessWidget {
      @override
      Widget build(context) => ListView(
        children: [
          FlatButton(
            onPressed: () {},
              child: ListTile(
              leading: Icon(Icons.looks_one),
              title: Text("First Link"),
            )
          ),
          FlatButton(
            onPressed: () {},
              child: ListTile(
              leading: Icon(Icons.looks_two),
              title: Text("Second Link"),
            )
          )
        ]
      );
    }

    On a smartphone, a common place to use that would be inside a Drawer (also known as a hamburger menu).

    Alternatives to that would be the BottomNavigationBar or the TabBar, in combination with the TabBarView, but with both we’d have to make more changes than are required with the drawer, so we’ll stick with the drawer.

    To only show the Drawer containing the Menu that we saw earlier on smaller screens, you’d write code that looks like the following snippet, checking the width using the MediaQuery.of(context) and passing a Drawer object to the Scaffold only if it’s less than some width value that we believe to be appropriate for our app:

    Scaffold(
        appBar: AppBar(/* ... */),
        drawer: MediaQuery.of(context).size.width < 500 ?
        Drawer(
          child: Menu(),
        ) :
        null,
        body: /* ... */
    )

    Now, let’s think about the body of the Scaffold. As the sample main content of our app, we’ll use the GridView that we built previously, which we keep in a separate widget named Content to avoid confusion:

    class Content extends StatelessWidget {
      final List elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"];
      @override
      Widget build(context) => GridView.builder(
        itemCount: elements.length,
        gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
          maxCrossAxisExtent: 130.0,
          crossAxisSpacing: 20.0,
          mainAxisSpacing: 20.0,
        ),
        itemBuilder: (context, i) => Card(
          child: Center(
            child: Padding(
              padding: EdgeInsets.all(8.0), child: Text(elements[i])
            )
          )
        )
      );
    }

    On bigger screens, the body itself may be a Row that shows two widgets: the Menu, which is restricted to a fixed width, and the Content filling the rest of the screen.

    On smaller screens, the entire body would be the Content.

    We’ll wrap everything in a SafeArea and a Center widget because sometimes Flutter web app widgets, especially when using Rows and Columns, end up outside of the visible screen area, and that is fixed with SafeArea and/or Center.

    This means the body of the Scaffold will be the following:

    SafeArea(
      child:Center(
        child: MediaQuery.of(context).size.width < 500 ? Content() :
        Row(
          children: [
            Container(
              width: 200.0,
              child: Menu()
            ),
            Container(
              width: MediaQuery.of(context).size.width-200.0,
              child: Content()
            )
          ]
        )
      )
    )

    Here is all of that put together:

    (Large preview)
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(context) => MaterialApp(
        home: HomePage()
      );
    }
    
    
    class HomePage extends StatelessWidget {
      @override
      Widget build(context) => Scaffold(
        appBar: AppBar(title: Text("test")),
        drawer: MediaQuery.of(context).size.width < 500 ? Drawer(
          child: Menu(),
        ) : null,
        body: SafeArea(
            child:Center(
              child: MediaQuery.of(context).size.width < 500 ? Content() :
              Row(
                children: [
                  Container(
                    width: 200.0,
                    child: Menu()
                  ),
                  Container(
                    width: MediaQuery.of(context).size.width-200.0,
                    child: Content()
                  )
                ]
              )
            )
        )
      );
    }
    
    class Menu extends StatelessWidget {
      @override
      Widget build(context) => ListView(
        children: [
          FlatButton(
            onPressed: () {},
              child: ListTile(
              leading: Icon(Icons.looks_one),
              title: Text("First Link"),
            )
          ),
          FlatButton(
            onPressed: () {},
              child: ListTile(
              leading: Icon(Icons.looks_two),
              title: Text("Second Link"),
            )
          )
        ]
      );
    }
    
    class Content extends StatelessWidget {
      final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"];
      @override
      Widget build(context) => GridView.builder(
        itemCount: elements.length,
        gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
          maxCrossAxisExtent: 130.0,
          crossAxisSpacing: 20.0,
          mainAxisSpacing: 20.0,
        ),
        itemBuilder: (context, i) => Card(
          child: Center(
            child: Padding(
              padding: EdgeInsets.all(8.0), child: Text(elements[i])
            )
          )
        )
      );
    }

    This is most of the stuff you’ll need as a general introduction to responsive UI in Flutter. Much of its application will depend on your app’s specific UI, and it’s hard to pinpoint exactly what you can do to make your app responsive, and you can take many approaches depending on your preference. Now, though, let’s see how we can make a more complete example into a responsive app, thinking about common app elements and UI flows.

    Putting It In Context: Making An App Responsive

    So far, we have just a screen. Let’s expand that into a two-screen app with working URL-based navigation!

    Creating A Responsive Login Page

    Chances are that your app has a login page. How can we make that responsive?

    Login screens on mobile devices are quite similar to each other usually. The space available isn’t much; it’s usually just a Column with some Padding around its widgets, and it contains TextFields for typing in a username and a password and a button to log in. So, a pretty standard (though not functioning, as that would require, among other things, a TextEditingController for each TextField) login page for a mobile app could be the following:

    Scaffold(
      body: Container(
        padding: const EdgeInsets.symmetric(
          vertical: 30.0, horizontal: 25.0
        ),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            Text("Welcome to the app, please log in"),
            TextField(
              decoration: InputDecoration(
                labelText: "username"
              )
            ),
            TextField(
              obscureText: true,
              decoration: InputDecoration(
                labelText: "password"
              )
            ),
            RaisedButton(
              color: Colors.blue,
              child: Text("Log in", style: TextStyle(color: Colors.white)),
              onPressed: () {}
            )
          ]
        ),
      ),
    )

    It looks fine on a mobile device, but those very wide TextFields start to look jarring on a tablet, let alone a bigger screen. However, we can’t just decide on a fixed width because phones have different screen sizes, and we should maintain a degree of flexibility.

    For example, through experimentation, we might find that the maximum width should be 500. Well, we would set the Container‘s constraints to 500 (I used a Container instead of Padding in the previous example because I knew where I was going with this) and we’re good to go, right? Not really, because that would cause the login widgets to stick to the left side of the screen, which might be even worse than stretching everything. So, we wrap in a Center widget, like this:

    Center(
      child: Container(
        constraints: BoxConstraints(maxWidth: 500),
        padding: const EdgeInsets.symmetric(
          vertical: 30.0, horizontal: 25.0
        ),
        child: Column(/* ... */)
      )
    )

    That already looks fine, and we haven’t even had to use either a LayoutBuilder or the MediaQuery.of(context).size. Let’s go one step further to make this look very good, though. It would look better, in my view, if the foreground part was in some way separated from the background. We can achieve that by giving a background color to what’s behind the Container with the input widgets, and keeping the foreground Container white. To make it look a little better, let’s keep the Container from stretching to the top and bottom of the screen on large devices, give it rounded corners, and give it a nice animated transition between the two layouts.

    All of that now requires a LayoutBuilder and an outer Container in order both to set a background color and to add padding all around the Container and not just on the sides only on larger screens. Also, to make the change in padding amount animated, we just need to turn that outer Container into an AnimatedContainer, which requires a duration for the animation, which we’ll set to half a second, which is Duration(milliseconds: 500) in code.

    Here’s that example of a responsive login page:

    (Large preview)
    class LoginPage extends StatelessWidget {
      @override
      Widget build(context) =>
        Scaffold(
          body: LayoutBuilder(
            builder: (context, constraints) {
              return AnimatedContainer(
                duration: Duration(milliseconds: 500),
                color: Colors.lightGreen[200],
                padding: constraints.maxWidth < 500 ? EdgeInsets.zero : EdgeInsets.all(30.0),
                child: Center(
                  child: Container(
                    padding: EdgeInsets.symmetric(
                      vertical: 30.0, horizontal: 25.0
                    ),
                    constraints: BoxConstraints(
                      maxWidth: 500,
                    ),
                    decoration: BoxDecoration(
                      color: Colors.white,
                      borderRadius: BorderRadius.circular(5.0),
                    ),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.spaceAround,
                      children: [
                        Text("Welcome to the app, please log in"),
                        TextField(
                          decoration: InputDecoration(
                            labelText: "username"
                          )
                        ),
                        TextField(
                          obscureText: true,
                          decoration: InputDecoration(
                            labelText: "password"
                          )
                        ),
                        RaisedButton(
                          color: Colors.blue,
                          child: Text("Log in", style: TextStyle(color: Colors.white)),
                          onPressed: () {
                            Navigator.pushReplacement(
                              context,
                              MaterialPageRoute(
                                builder: (context) => HomePage()
                              )
                            );
                          }  
                        )
                      ]
                    ),
                  ),
                )
              );
            }
          )
       );
    }

    As you can see, I’ve also changed the RaisedButton‘s onPressed to a callback that navigates us to a screen named HomePage (which could be, for example, the view we built previously with a GridView and a menu or a drawer). Now, though, that navigation part is what we’re going to focus on.

    Named Routes: Making Your App’s Navigation More Like A Proper Web App

    A common thing for web apps to have is the ability to change screens based on the URL. For example going to https://appurl/login should give you something different than https://appurl/somethingelse. Flutter, in fact, supports named routes, which have two purposes:

    1. In a web app, they have exactly that feature that I mentioned in the previous sentence.
    2. In any app, they allow you to predefine routes for your app and give them names, and then be able to navigate to them just by specifying their name.

    To do that, we need to change the MaterialApp constructor to one that looks like the following:

    MaterialApp(
      initialRoute: "/login",
      routes: {
        "/login": (context) => LoginPage(),
        "/home": (context) => HomePage()
      }
    );

    And then we can switch to a different route by using Navigator.pushNamed(context, routeName) and Navigator.pushReplacementNamed(context, routeName), instead of Navigator.push(context, route) and Navigator.pushReplacement(context, route).

    Here is that applied to the hypothetical app we built in the rest of this article. You can’t really see named routes in action in DartPad, so you should try this out on your own machine with flutter run, or check the example in action:

    (Large preview)
    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(context) =>
        MaterialApp(
          initialRoute: "/login",
          routes: {
            "/login": (context) => LoginPage(),
            "/home": (context) => HomePage()
          }
        );
    }
    
    class LoginPage extends StatelessWidget {
      @override
      Widget build(context) =>
        Scaffold(
          body: LayoutBuilder(
            builder: (context, constraints) {
              return AnimatedContainer(
                duration: Duration(milliseconds: 500),
                color: Colors.lightGreen[200],
                padding: constraints.maxWidth < 500 ? EdgeInsets.zero : const EdgeInsets.all(30.0),
                child: Center(
                  child: Container(
                    padding: const EdgeInsets.symmetric(
                      vertical: 30.0, horizontal: 25.0
                    ),
                    constraints: BoxConstraints(
                      maxWidth: 500,
                    ),
                    decoration: BoxDecoration(
                      color: Colors.white,
                      borderRadius: BorderRadius.circular(5.0),
                    ),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.spaceAround,
                      children: [
                        Text("Welcome to the app, please log in"),
                        TextField(
                          decoration: InputDecoration(
                            labelText: "username"
                          )
                        ),
                        TextField(
                          obscureText: true,
                          decoration: InputDecoration(
                            labelText: "password"
                          )
                        ),
                        RaisedButton(
                          color: Colors.blue,
                          child: Text("Log in", style: TextStyle(color: Colors.white)),
                          onPressed: () {
                            Navigator.pushReplacementNamed(
                              context,
                              "/home"
                            );
                          }
                        )
                      ]
                    ),
                  ),
                )
              );
            }
          )
       );
    }
    
    
    class HomePage extends StatelessWidget {
      @override
      Widget build(context) => Scaffold(
        appBar: AppBar(title: Text("test")),
        drawer: MediaQuery.of(context).size.width < 500 ? Drawer(
          child: Menu(),
        ) : null,
        body: SafeArea(
            child:Center(
              child: MediaQuery.of(context).size.width < 500 ? Content() :
              Row(
                children: [
                  Container(
                    width: 200.0,
                    child: Menu()
                  ),
                  Container(
                    width: MediaQuery.of(context).size.width-200.0,
                    child: Content()
                  )
                ]
              )
            )
        )
      );
    }
    
    class Menu extends StatelessWidget {
      @override
      Widget build(context) => ListView(
        children: [
          FlatButton(
            onPressed: () {},
              child: ListTile(
              leading: Icon(Icons.looks_one),
              title: Text("First Link"),
            )
          ),
          FlatButton(
            onPressed: () {},
              child: ListTile(
              leading: Icon(Icons.looks_two),
              title: Text("Second Link"),
            )
          ),
          FlatButton(
            onPressed: () {Navigator.pushReplacementNamed(
              context, "/login");},
              child: ListTile(
              leading: Icon(Icons.exit_to_app),
              title: Text("Log Out"),
            )
          )
        ]
      );
    }
    
    class Content extends StatelessWidget {
      final List<String> elements = ["Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "A Million Billion Trillion", "A much, much longer text that will still fit"];
      @override
      Widget build(context) => GridView.builder(
        itemCount: elements.length,
        gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
          maxCrossAxisExtent: 130.0,
          crossAxisSpacing: 20.0,
          mainAxisSpacing: 20.0,
        ),
        itemBuilder: (context, i) => Card(
          child: Center(
            child: Padding(
              padding: EdgeInsets.all(8.0), child: Text(elements[i])
            )
          )
        )
      );
    }

    Onward With Your Flutter Adventure

    That should give you an idea of what you can do with Flutter on bigger screens, specifically on the web. It’s a lovely framework, very easy to use, and its extreme cross-platform support only makes it more essential to learn and start using. So, go ahead and start trusting Flutter for web apps, too!

    Further Resources

    • “Desktop shells”, GitHub
      The current, always up-to-date state of Flutter on desktop
    • “Desktop support for Flutter”, Flutter
      Information about the fully supported desktop platforms
    • “Web support for Flutter”, Flutter
      Information about Flutter for the web
    • “All Samples”, Flutter
      A curated list of Flutter samples and apps
    Smashing Editorial
    (ra, yk, il, al)

    From our sponsors: Responsive Web And Desktop Development With Flutter

    Posted on 22nd April 2020Web Design
    FacebookshareTwittertweetGoogle+share

    Related posts

    Archived
    22nd March 2023
    Archived
    18th March 2023
    Archived
    20th January 2023
    Thumbnail for 25788
    Handling Continuous Integration And Delivery With GitHub Actions
    19th October 2020
    Thumbnail for 25778
    A Monthly Update With New Guides And Community Resources
    19th October 2020
    Thumbnail for 25781
    Supercharge Testing React Applications With Wallaby.js
    19th October 2020
    Latest News
    • Archived
      22nd March 2023
    • Archived
      18th March 2023
    • Archived
      20th January 2023
    • 20201019 ML Brief
      19th October 2020
    • Thumbnail for 25788
      Handling Continuous Integration And Delivery With GitHub Actions
      19th October 2020
    • Thumbnail for 25786
      The Future of CX with Larry Ellison
      19th October 2020
    News Categories
    • Digital Marketing
    • Web Design

    Our services

    Website Design
    Website Design

    A website is an important part of any business. Professional website development is an essential element of a successful online business.

    We provide website design services for every type of website imaginable. We supply brochure websites, E-commerce websites, bespoke website design, custom website development and a range of website applications. We love developing websites, come and talk to us about your project and we will tailor make a solution to match your requirements.

    You can contact us by phone, email or send us a request through our online form and we can give you a call back.

    More Information

    Digital Marketing
    Digital Marketing

    Our digital marketeers have years of experience in developing and excuting digital marketing strategies. We can help you promote your business online with the most effective methods to achieve the greatest return for your marketing budget. We offer a full service with includes the following:

    1. Social Media Marketing

    2. Email & Newsletter Advertising

    3. PPC - Pay Per Click

    4. A range of other methods are available

    More Information

    SEO
    SEO Services

    SEO is an essential part of owning an online property. The higher up the search engines that your website appears, the more visitors you will have and therefore the greater the potential for more business and increased profits.

    We offer a range of SEO services and packages. Our packages are very popular due to the expanse of on-page and off-page SEO services that they cover. Contact us to discuss your website and the SEO services that would best suit to increase your websites ranking.

    More Information

    E-commerce
    E-commerce Websites

    E-commerce is a rapidly growing area with sales online increasing year on year. A professional E-commerce store online is essential to increase sales and is a reflection of your business to potential customers. We provide professional E-commerce websites custom built to meet our clients requirements.

    Starting to sell online can be a daunting task and we are here to make that journey as smooth as possible. When you work with Cunningham Web Solutions on your E-commerce website, you will benefit from the experience of our team and every detail from the website design to stock management is carefully planned and designed with you in mind.

    More Information

    Social Media Services
    Social Media Services

    Social Media is becoming an increasingly effective method of marketing online. The opportunities that social media marketing can offer are endless and when managed correctly can bring great benefits to every business.

    Social Media Marketing is a low cost form of advertising that continues to bring a very good ROI for our clients. In conjuction with excellent website development and SEO, social media marketing should be an essential part of every digital marketing strategy.

    We offer Social Media Management packages and we also offer Social Media Training to individuals and to companies. Contact us to find out more.

    More Information

    Cunningham Web Solutions
    © Copyright 2025 | Cunningham Web Solutions
    • Home
    • Our Services
    • FAQ's
    • Account Services
    • Privacy Policy
    • Contact Us