Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Flutter

Flutter Getting Started: Tutorial 5 Grid

5.00/5 (2 votes)
21 Jul 2018CPOL4 min read 31.4K   472  
Let's explore Flutter Grid view and MediaQuery

Introduction

Flutter GridView is almost the same as ListView except it provides a 2D view comparison to ListView single direction view. It is also quite a popular widget across mobile app development. If you don't believe me, open any ecommerce app in your mobile - it is definitely dependent either on ListView or GridView to display data, for e.g.:

Here, Amazon mobile app displays data in grid:

Image 1

Another one, PayTM, one of popular online wallet service app in India, widely used grid layout to display different products:

Image 2

Background

The ultimate aim of this article to achieve a view similar to this:

Image 3

However, if you have noticed the above image, that is in landscape mode. So here is what I am going to do in this article. When the application is in portrait mode, MobileApp would display item in ListView and when it’s in landscape mode, show items in 3 items per row grid. I also explore creating custom widget, by moving gridview implementation in a separate class.

Using the Code

I am going to build on my previous article Flutter Getting Started: Tutorial 4 ListView, there I have already created ListView based application, here the initial project structure and initial UI.

Image 4

Here is the initial code from which we start building on:

C#
class HomePage extends StatelessWidget {
  final List<City> _allCities = City.allCities();

  HomePage() {}
  final GlobalKey scaffoldKey = new GlobalKey();
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        key: scaffoldKey,
        appBar: new AppBar(
          title: new Text(
            "Cites around world",
            style: new TextStyle(
                fontSize: 18.0,
                fontWeight: FontWeight.bold,
                color: Colors.black87),
          ),
        ),
        body: new Padding(
            padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0),
            child: getHomePageBody(context)));
  }

  getHomePageBody(BuildContext context) {
   
      return ListView.builder(
        itemCount: _allCities.length,
        itemBuilder: _getListItemUI,
        padding: EdgeInsets.all(0.0),
      );   
  }

  Widget _getListItemUI(BuildContext context, int index,
      {double imgwidth: 100.0}) {
    return new Card(
        child: new Column(
      children: <Widget>[
        new ListTile(
          leading: new Image.asset(
            "assets/" + _allCities[index].image,
            fit: BoxFit.fitHeight,
            width: imgwidth,
          ),
          title: new Text(
            _allCities[index].name,
            style: new TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold),
          ),
          subtitle: new Column(
              mainAxisAlignment: MainAxisAlignment.start,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                new Text(_allCities[index].country,
                    style: new TextStyle(
                        fontSize: 13.0, fontWeight: FontWeight.normal)),
                new Text('Population: ${_allCities[index].population}',
                    style: new TextStyle(
                        fontSize: 11.0, fontWeight: FontWeight.normal)),
              ]),
          onTap: () {
            _showSnackBar(context, _allCities[index]);
          },
        )
      ],
    ));
  }

  _showSnackBar(BuildContext context, City item) {
    final SnackBar objSnackbar = new SnackBar(
      content: new Text("${item.name} is a city in ${item.country}"),
      backgroundColor: Colors.amber,
    );

    Scaffold.of(context).showSnackBar(objSnackbar);
  }
}

Before I start on the actual quest, let me give you a brief of what I have done above:

  • I have created a simple ListView using ListView.builder, which gives flexibility of creating infinite listitem view, as it calls callback function only for those items, which could be displayed on the screen.
  • I am displaying City information like City Landmark image, followed by CityName, Country the city belongs and her population.
  • Lastly on clicking, it shows a small self disappearing message at the bottom of the screen, known as SnackBar.

Now from here, our work starts, as I mentioned earlier, we would be refactoring a new widget into a different class, to keep our code modular and increase clarity of code. So create a new folder widget under lib folder and add a new DART file mygridview.dart.

Once you add the file, first import material widget, by importing 'package:flutter/material.dart' and then add MyGridView class which extends our favourite StatelessWidget and override Build function, so bare bone code looks like this:

C#
import 'package:flutter/material.dart';
import 'package:flutter5_gridlist/model/city.dart';

class MyGridView extends StatelessWidget {
  final List<City> allCities;
  MyGridView({Key key, this.allCities}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return null;
  }
}

I would now add basic GridView just displaying the City name, so I will add the following code in overridden Build function:

C#
@override
Widget build(BuildContext context) {
  return GridView.count(
    crossAxisCount: 3,
    padding: EdgeInsets.all(16.0),
     childAspectRatio: 8.0,
    children: _getGridViewItems(context),
  );
}
_getGridViewItems(BuildContext context){
  List<Widget> allWidgets = new List<Widget>();
  for (int i = 0; i < allCities.length; i++) {
    var widget = new Text(allCities[i].name);
    allWidgets.add(widget);
  };
  return allWidgets;
}

Explanation of the above code:

  • GridView.count method would provide GridView widget for application
  • crossAxisCount property is used to let mobile application know how many items we want to show in each row
  • children property will contain all the widgets you wish to show when page is loaded
  • childAspectRatio, which is the ratio of the cross-axis to the main-axis extent of each child, since I am displaying the name, I will keep the same at 8.0 so that margin between two tiles is reduced

Here is what the UI looks like:

Image 5

Now let's make the UI similar to what we are seeing in the ListView. Here, I created a new function which will send City class in the form of Card.

C#
// Create individual item
_getGridItemUI(BuildContext context, City item) {
  return new InkWell(
      onTap: () {
        _showSnackBar(context, item);
      },
      child: new Card(
        child: new Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            new Image.asset(
              "assets/" + item.image,
              fit: BoxFit.fill,
              
            ),
            new Expanded(
                child: new Center(
                    child: new Column(
              children: <Widget>[
                new SizedBox(height: 8.0),
                new Text(
                  item.name,
                  style: new TextStyle(
                    fontSize: 20.0,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                new Text(item.country),
                new Text('Population: ${item.population}')
              ],
            )))
          ],
        ),
        elevation: 2.0,
        margin: EdgeInsets.all(5.0),
      ));
}

Explanation of the above code:

  • I am using Inkwell class, since Card class doesn't support gesture directly, So I wrapped it inside InkWell class, to utilize its onTap event for displaying SnackBar.
  • Rest of the code is similar to ListView card except no width is specified.
  • Also, since we are displaying a complete card, don't forget to change childAspectRatio from 8.0 to 8.0/9.0, as we require more height .

Before I forget, I told at the start of the application, I will show ListView in portrait orientation and GridView in landscape orientation, so for achieving it MediaQuery class to identify the orientation. Whenever the orientation is changed, i.e., you tilt your mobile, the widget are redrawn, so Build function is called, there you could take a decision what code should be called. So in homepage.dart class, we will use the following function to handle Orientation changes.

C#
getHomePageBody(BuildContext context) {
  if (MediaQuery.of(context).orientation == Orientation.portrait)
    return ListView.builder(
      itemCount: _allCities.length,
      itemBuilder: _getListItemUI,
      padding: EdgeInsets.all(0.0),
    );
  else
    return new MyGridView(allCities: _allCities);
}

So our final UI would be like this:

Image 6

End of tutorial.

Points of Interest

Please go through these articles. It might give you a headway, where the wind is actually flowing:

  1. https://material.io/design/components/cards.html
  2. Github: https://github.com/thatsalok/FlutterExample/tree/master/flutter5_gridlist

Flutter Tutorials

  1. Flutter Getting Started: Tutorial 1 Basics
  2. Flutter Getting Started: Tutorial 2 - StateFulWidget
  3. Flutter Getting Started: Tutorial 3 Navigation
  4. Flutter Getting Started: Tutorial 4 ListView

Dart Tutorials

  1. DART2 Prima Plus - Tutorial 1
  2. DART2 Prima Plus - Tutorial 2 - LIST
  3. DART2 Prima Plus - Tutorial 3 - MAP

History

  • 22-July-2018: First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)