Getting Started With Flutter

Dive into the Flutter framework, which lets you build iOS, Android, web and desktop apps with a single codebase, by writing a cross-platform app using VS Code. By Jonathan Sande.

4.9 (12) · 3 Reviews

Download materials
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

Parsing to Custom Types

In the previous section, the JSON parser took the members in the JSON response and assigned them to _members. Although you defined the list as dynamic, the actual type that Dart put in the list was Map, a data structure that holds key-value pairs. This is the equivalent of a Map in Kotlin or a Dictionary in Swift.

However, you also want to be able to use your own custom types.

Add a new Member type at the bottom of main.dart:

class Member {
  Member(this.login);
  final String login;
}

Member has a constructor that sets the login property when you create a member object.

Update the _members declaration in _GHFlutterState so that it’s a list of Member objects:

final _members = <Member>[];

You used final in place of var because, instead of reassigning a new list to _members, you’re going to add items to the existing list now.

Replace setState in _loadData with the following code:

setState(() {
  final dataList = json.decode(response.body) as List;
  for (final item in dataList) {
    final login = item['login'] as String? ?? '';
    final member = Member(login);
    _members.add(member);
  }
});

This turns each decoded map into a Member and adds it to the list of members.

Note: Curious about the single ? and double ?? question marks? Check out the Dart Basics tutorial for more details about these operators.

Flutter is still complaining inside ListTile because it was expecting Map rather than Member. Replace the title line of ListTile with the following:

title: Text('${_members[i].login}', style: _biggerFont),

You’ll see an error if you try a hot reload, so hot restart. You’ll see the same screen as before, except that it now uses your new Member class.

Downloading Images With NetworkImage

In GitHub, each member has a URL for their avatar. Your next improvement is to add that avatar to the Member class and show the avatars in the app.

Update Member to add an avatarUrl property. It should look like this now:

class Member {
  Member(this.login, this.avatarUrl);
  final String login;
  final String avatarUrl;
}

Since avatarUrl is now a required parameter, Flutter complains at you in _loadData. Replace the setState callback in _loadData with the following updated version:

setState(() {
  final dataList = json.decode(response.body) as List;
  for (final item in dataList) {
    final login = item['login'] as String? ?? '';
    final url = item['avatar_url'] as String? ?? '';
    final member = Member(login, url);
    _members.add(member);
  }
});

The code above uses the avatar_url key to look up the URL value in the map parsed from JSON, then set it to the url string, which you pass on to Member.

Now that you have access to the URL for the avatar, add it to your ListTile. Replace _buildRow with the following:

Widget _buildRow(int i) {
  return Padding(
    padding: const EdgeInsets.all(16.0),
    child: ListTile(
      title: Text('${_members[i].login}', style: _biggerFont),
      leading: CircleAvatar(
        backgroundColor: Colors.green,
        backgroundImage: NetworkImage(_members[i].avatarUrl),
      ),
    ),
  );
}

This adds a CircleAvatar to the leading edge of your ListTile. While you’re waiting for the images to download, the background of the CircleAvatar will be green.

Do a hot restart rather than a hot reload. You’ll see your member avatars in each row:

Members with avatars

Cleaning up the Code

Most of your code is now in main.dart. To make the code a little cleaner, you’ll refactor classes into their own files.

Create files named member.dart and ghflutter.dart in the lib folder. Move Member into member.dart and both _GHFlutterState and GHFlutter into ghflutter.dart.

You won’t need any import statements in member.dart, but the imports in ghflutter.dart should be:

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'member.dart';
import 'strings.dart' as strings;

You also need to update the imports in main.dart. Replace the entire file with the following:

import 'package:flutter/material.dart';
import 'ghflutter.dart';
import 'strings.dart' as strings;

void main() => runApp(const GHFlutterApp());

class GHFlutterApp extends StatelessWidget {
  const GHFlutterApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: strings.appTitle,
      // TODO: add theme here
      home: const GHFlutter(),
    );
  }
}

Save everything and rerun the app. You won’t see a change, but the code is now a little cleaner. :]

You might have noticed a new comment: // TODO: add theme here. There, you’ll make one last change before the tutorial is over.

Adding a Theme

Your final improvement is to easily add a theme to the app by adding a theme attribute to the MaterialApp you created in main.dart.

Find // TODO: add theme here and replace it with the following:

theme: ThemeData(primaryColor: Colors.green.shade800), 

Here, you’re using a shade of green as a Material Design color value for the theme.

Save and hot reload to see the new theme in action:

Running in the Android emulator

The app screenshots so far have been from the Android emulator. You can also run the final themed app in iOS Simulator:

App running on iOS Simulator

And here’s how it looks on the Chrome web browser:

App running on Chrome

Feel free to run it as a Windows, Mac or Linux app as well. It only requires a little extra setup and adding desktop support to your app. On macOS, you should also give the app permission to access the internet.

Here’s the app running on macOS:

App running on macOS

Now that’s what you call cross-platform! :]

Where to Go From Here?

Download the completed project files by clicking the Download Materials button at the top or bottom of the tutorial.

You can open the project in either VS Code or Android Studio.

Open it in VS Code by opening the root folder. You’ll need to fetch packages before running the project. Do so by pressing Command-Shift-P on MacOS or Control-Shift-P on Windows or Linux to open the command palette and running the Flutter: Get Packages command.

To open the project in Android Studio, choose Open an existing project from the Welcome to Android Studio screen and navigate to choose the root folder of the final project. Then choose Get dependencies on the 'Pub get' has not been run line in Android Studio.

There’s a lot more to learn about Flutter and Dart. Here are some places to start:

And you’ll definitely want to browse the raywenderlich.com Flutter library! There’s an ever-increasing selection of books, articles and videos to help you on your Flutter journey.

We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!