In this first Flutter tutorial, we will start building a native Android and iOS apps with some useful features. It means, not just creating a basic hello world or basic elements. The feature that will cover is accessing local data and display to a Grid or List elements, display data details, show Google Maps, and navigate through required screens.
This tutorial divided into several steps:
- Step #1: Preparation
- Step #2: Create a Flutter Application
- Step #3: Create a JSON File
- Step #4: Display a List of Data
- Step #5: Display Data Details
- Step #6: Display Google Maps
- Step #7: Run and Test Flutter Apps to Android and iOS
The following tools, frameworks, and libraries are required for this tutorial:
- Flutter SDK
- Android SDK
- google_maps_flutter dependency
- XCode
- Terminal (on Mac/Linux) or CMD (on Windows)
- IDE (Android Studio/IntelliJ/Visual Studio Code)
Also, you can watch this tutorial on our YouTube channel here. Please give it like, comment, share, and subscribe.
Let get started to the main steps!
Step #1: Preparation
Install Flutter SDK
To install flutter SDK, first, we have to download flutter_macos_v1.12.13+hotfix.5-stable.zip.
Extract the file to your desired location.
cd ~/development
unzip ~/Downloads/flutter_macos_v1.12.13+hotfix.5-stable.zip
Next, add flutter tools to the path.
export PATH="$PATH:~/development/flutter/bin"
Next, to make iOS and Android binaries can be downloaded ahead of time, type this Flutter command.
flutter precache
Check the Required Dependencies
To check the environment and displays a report to the terminal window to find dependencies that required to install, type this command.
flutter doctor
We have this summary in the Terminal.
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Mac OS X 10.14.6 18G2022,
locale en-ID)
[!] Android toolchain - develop for Android devices (Android SDK version
29.0.0-rc1)
! Some Android licenses not accepted. To resolve this, run: flutter doctor
--android-licenses
[✓] Xcode - develop for iOS and macOS (Xcode 10.3)
[✓] Android Studio (version 3.4)
[✓] VS Code (version 1.37.1)
[!] Connected device
! No devices available
! Doctor found issues in 2 categories.
To fixes, the issues like this, just connect the Android device and update the Android license by type this command.
flutter doctor --android-licenses
Type `y` for every question that displayed in the terminal. Next, check the connected Android device by type this command.
adb devices
List of devices attached
FUZDON75YLOVVC5S device
Setup IDE
We need to set up the IDE to make it working with the Flutter app easily and compatibly. Start the Android Studio (we will use this IDE) then open Android Studio Menu -> Preference.
Choose plugins in the left pane.
Type Flutter in the plugins marketplace then presses Enter. Next, click the Install button on the Flutter. If there's a prompt to install Dart, click Yes. Restart the IDE when prompt to ask restart.
Now, the Flutter is ready to develop Android and iOS mobile apps.
Step #2: Create a Flutter Application
After the IDE setup complete, it will back to the Android starting dialog.
Choose `Start a New Flutter Project` then choose `Flutter Application`.
Click the Next Button then fill the required fields and choose the previously installed Flutter SDK path.
Click the next button then fill the package name with your own domain.
Click the Finish button and the Flutter application creation in progress. Next, run the Flutter application for the first time. In the Android Studio toolbar, choose the device and main.dart then click the play button.
Here they are the Flutter application look like in Android device or iOS simulator.
Step #3: Create a JSON File
We will display local data in JSON format. For that, create the assets, data, and logos folder first at the root of the project folder.
mkdir assets
mkdir assets/data
mkdir assets/logos
You can add or create directories from the IDE as well. Next, add or create a JSON file in the assets/data folder.
touch assets/data/team.json
Next, open and edit that file using your IDE then add these lines of JSON data.
[
{
"id": 1,
"name": "Arsenal",
"image": "arsenal.png",
"location": "London (Holloway)",
"stadium": "Emirates Stadium",
"capacity": 60704,
"manager": "Mikel Arteta",
"captain": "Pierre-Emerick Aubameyang",
"lat": 51.554928,
"lng": -0.108449
},
{
"id": 2,
"name": "Aston Villa",
"image": "aston-villa.png",
"location": "Birmingham",
"stadium": "Villa Park",
"capacity": 42785,
"manager": "Dean Smith",
"captain": "Jack Grealish",
"lat": 52.509132,
"lng": -1.884767
},
{
"id": 3,
"name": "Bournemouth",
"image": "bornemouth.png",
"location": "Bournemouth",
"stadium": "Dean Court",
"capacity": 11364,
"manager": "Eddie Howe",
"captain": "Simon Francis",
"lat": 50.73528,
"lng": -1.83829
},
{
"id": 4,
"name": "Brighton & Hove Albion",
"image": "brighton.png",
"location": "Brighton",
"stadium": "Falmer Stadium",
"capacity": 30666,
"manager": "Graham Potter",
"captain": "Lewis Dunk",
"lat": 50.861607,
"lng": -0.083716
},
{
"id": 5,
"name": "Burnley",
"image": "burnley.png",
"location": "Burnley",
"stadium": "Turf Moor",
"capacity": 21944,
"manager": "Sean Dyche",
"captain": "Ben Mee",
"lat": 53.789017,
"lng": -2.230187
},
{
"id": 6,
"name": "Chelsea",
"image": "chelsea.png",
"location": "London (Fulham)",
"stadium": "Stamford Bridge",
"capacity": 41631,
"manager": "Frank Lampard",
"captain": "César Azpilicueta",
"lat": 51.481697,
"lng": -0.190957
},
{
"id": 7,
"name": "Crystal Palace",
"image": "crystal-palace.png",
"location": "London (Selhurst)",
"stadium": "Selhurst Park",
"capacity": 26047,
"manager": "Roy Hodgson",
"captain": "Luka Milivojević",
"lat": 51.39828,
"lng": -0.085489
},
{
"id": 8,
"name": "Everton",
"image": "everton.png",
"location": "Liverpool (Walton)",
"stadium": "Goodison Park",
"capacity": 39221,
"manager": "Marco Silva",
"captain": "Séamus Coleman",
"lat": 53.438812,
"lng": -2.966331
},
{
"id": 9,
"name": "Leicester City",
"image": "leicester.png",
"location": "Leicester",
"stadium": "King Power Stadium",
"capacity": 32312,
"manager": "Brendan Rodgers",
"captain": "Wes Morgan",
"lat": 52.6204,
"lng": -1.142147
},
{
"id": 10,
"name": "Liverpool",
"image": "liverpool.png",
"location": "Liverpool (Anfield)",
"stadium": "Anfield",
"capacity": 54074,
"manager": "Jürgen Klopp",
"captain": "Jordan Henderson",
"lat": 53.430847,
"lng": -2.960844
},
{
"id": 11,
"name": "Manchester City",
"image": "manchester-city.png",
"location": "Manchester",
"stadium": "City of Manchester Stadium",
"capacity": 55017,
"manager": "Pep Guardiola",
"captain": "David Silva",
"lat": 53.483177,
"lng": -2.200427
},
{
"id": 12,
"name": "Manchester United",
"image": "manchester-united.png",
"location": "Manchester",
"stadium": "Old Trafford",
"capacity": 74879,
"manager": "Ole Gunnar Solskjær",
"captain": "Ashley Young",
"lat": 53.463078,
"lng": -2.291334
},
{
"id": 13,
"name": "Newcastle United",
"image": "newcastle-united.png",
"location": "Newcastle",
"stadium": "St James Park",
"capacity": 52354,
"manager": "Steve Bruce",
"captain": "Jamaal Lascelles",
"lat": 54.975582,
"lng": -1.621661
},
{
"id": 14,
"name": "Norwich City",
"image": "norwich-city.png",
"location": "Norwich",
"stadium": "Carrow Road",
"capacity": 27244,
"manager": "Daniel Farke",
"captain": "Grant Hanley",
"lat": 52.62222,
"lng": 1.309328
},
{
"id": 15,
"name": "Sheffield United",
"image": "sheffield-united.png",
"location": "Sheffield",
"stadium": "Bramall Lane",
"capacity": 32702,
"manager": "Chris Wilder",
"captain": "Billy Sharp",
"lat": 53.370373,
"lng": -1.47088
},
{
"id": 16,
"name": "Southampton",
"image": "southampton.png",
"location": "Southampton",
"stadium": "St Marys Stadium",
"capacity": 32384,
"manager": "Ralph Hasenhüttl",
"captain": "Pierre-Emile Højbjerg",
"lat": 50.90586,
"lng": -1.390941
},
{
"id": 17,
"name": "Tottenham Hotspur",
"image": "tottenham-hotspur.png",
"location": "London (Tottenham)",
"stadium": "Tottenham Hotspur Stadium",
"capacity": 62214,
"manager": "José Mourinho",
"captain": "Hugo Lloris",
"lat": 51.60432,
"lng": -0.066381
},
{
"id": 18,
"name": "Watford",
"image": "watford.png",
"location": "Watford",
"stadium": "Vicarage Road",
"capacity": 20400,
"manager": "Quique Sánchez Flores",
"captain": "Troy Deeney",
"lat": 51.64996,
"lng": -0.401525
},
{
"id": 19,
"name": "West Ham United",
"image": "westham-united.png",
"location": "London (Stratford)",
"stadium": "London Stadium",
"capacity": 60000,
"manager": "Manuel Pellegrini",
"captain": "Mark Noble",
"lat": 51.53875,
"lng": -0.016625
},
{
"id": 20,
"name": "Wolverhampton Wanderers",
"image": "wolverhampton.png",
"location": "Wolverhampton",
"stadium": "Molineux Stadium",
"capacity": 32050,
"manager": "Nuno Espírito Santo",
"captain": "Conor Coady",
"lat": 52.5903,
"lng": -2.130418
}
]
As you see, there are the image fields in each JSON data. So, we need to add the image files to the assets/logos folder. You can get those files from our GitHub https://github.com/didinj/flutter-android-ios-tutorial.git or you can get your own image files. Next, we need to register those assets to the pubspec.yaml file. Open and edit pubspec.yaml file then adds these lines of YAML code after the uses-material-design line.
# The following section is specific to Flutter.
flutter:
uses-material-design: true
assets:
- assets/logos/
- assets/data/team.json
Next, to access data easily, we need to convert that JSON file to the Dart object. For that, create a Dart object file as a reference for the converted JSON data.
touch lib/team.dart
Open and edit that file then add these lines of Dart class that contain the class name, fields with final keyword and data type, constructor, and mapping from JSON string.
class Team {
final int id;
final String name;
final String image;
final String location;
final String stadium;
final int capacity;
final String manager;
final String captain;
final double lat;
final double lng;
Team(this.id, this.name, this.image, this.location, this.stadium,
this.capacity, this.manager, this.captain, this.lat, this.lng);
factory Team.fromJson(Map<String, dynamic> json) {
return new Team(
json['id'] as int,
json['name'] as String,
json['image'] as String,
json['location'] as String,
json['stadium'] as String,
json['capacity'] as int,
json['manager'] as String,
json['captain'] as String,
json['lat'] as double,
json['lng'] as double);
}
}
Step #4: Display a List of Data
We will display the list of data in a separate Dart file that will call from the main.dart home page body. So, the whole structure of widgets that display a list of data at least like this mockup.
Next, add a dart file for building the ListView widget.
touch lib/teamlist.dart
Open and edit that file then add these imports of Flutter material, detail page, and object file.
import 'package:flutter/material.dart';
import 'package:flutter_tutorial_app/detailwidget.dart';
import 'package:flutter_tutorial_app/team.dart';
Add TeamList class.
class TeamList extends StatelessWidget {
}
On the first line of the class body, declare a List of Team variables with the final keyword and TeamList constructor with Key and object fields.
final List<Team> team;
TeamList({Key key, this.team}) : super(key: key);
Add an overridden widget to build a ListView.
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: team == null ? 0 : team.length,
itemBuilder: (BuildContext context, int index) {
return
Card(
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailWidget(team[index])),
);
},
child: Row(
children: <Widget>[
Container(
padding: const EdgeInsets.all(8.0),
alignment: Alignment.topLeft,
child: Image(
image: AssetImage('assets/logos/' + team[index].image),
height: 60,
width: 60,
),
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(team[index].name, style: Theme.of(context).textTheme.title),
Text(team[index].location, style: TextStyle(color: Colors.black.withOpacity(0.8)))
],
),
)
],
),
)
);
});
That ListView widget has an item that contains Card widget. Card widget has tappable InkWell widget as its child. InkWell widget has a Row widget as its child. Row widget has a Container and Expanded widget as it children. Container widget has an Image widget as its child. Expanded widgets have Text widgets as it children.
The InkWell widget has an onTap event with an action to Navigate to the details page. Container, Column, Image, and Text have their own properties to adjust the style or layout.
Notes: Keep in mind, every widget that uses the child only has one widget as its child. If you need to put more than one widget to the parent widget, use children: <Widget> property.
Next, open and edit lib/main.dart then replace all Dart codes with these lines of codes to display the ListView in the main home page.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:flutter_tutorial_app/team.dart';
import 'package:flutter_tutorial_app/teamlist.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final String title = 'Premier League Team';
List data;
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: new Container(
child: new Center(
child: new FutureBuilder(
future: DefaultAssetBundle.of(context).loadString('assets/data/team.json'),
builder: (context, snapshot) {
List<Team> teams = parseJosn(snapshot.data.toString());
return teams.length > 0? new TeamList(team: teams): new Center(child: new CircularProgressIndicator());
},
)
),
),
),
);
}
List<Team> parseJosn(String response) {
if(response==null){
return [];
}
final parsed = json.decode(response.toString()).cast<Map<String, dynamic>>();
return parsed.map<Team>((json) => new Team.fromJson(json)).toList();
}
}
Step #5: Display Data Details
We will display data details to another page that opened when tapping on a list item in the list page. For that, create a Dart file in the lib folder first.
touch lib/detailwidget.dart
We will use a scrollable Card widget to display a detail to prevent overflow if the Card content is longer. So, the layout structure of the widgets combination at least like this mockup.
Next, open and edit lib/detailwidget.dart then add these imports of Flutter material, mapswidget page, and team object.
import 'package:flutter/material.dart';
import 'package:flutter_tutorial_app/mapswidget.dart';
import 'package:flutter_tutorial_app/team.dart';
Add a DetailWidget class that extends StatefulWidget. This class has a constructor with an object field, a field of Team object, and _DetailWidgetState that build the view for data detail.
class DetailWidget extends StatefulWidget {
DetailWidget(this.team);
final Team team;
@override
_DetailWidgetState createState() => _DetailWidgetState();
}
Add a _DetailWidgetState class that implementing all required widgets to display data details.
class _DetailWidgetState extends State<DetailWidget> {
_DetailWidgetState();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.team.name),
),
body: SingleChildScrollView(
child: Container(
padding: EdgeInsets.all(20.0),
child: Card(
child: Container(
padding: EdgeInsets.all(10.0),
width: 440,
child: Column(
children: <Widget>[
Container(
margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
child: Image(image: AssetImage('assets/logos/' + widget.team.image))
),
Container(
margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
child: Column(
children: <Widget>[
Text('City:', style: TextStyle(color: Colors.black.withOpacity(0.8))),
Text(widget.team.location, style: Theme.of(context).textTheme.title)
],
),
),
Container(
margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
child: Column(
children: <Widget>[
Text('Stadium:', style: TextStyle(color: Colors.black.withOpacity(0.8))),
Text(widget.team.stadium, style: Theme.of(context).textTheme.title)
],
),
),
Container(
margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
child: Column(
children: <Widget>[
Text('Stadium Capacity:', style: TextStyle(color: Colors.black.withOpacity(0.8))),
Text(widget.team.capacity.toString(), style: Theme.of(context).textTheme.title)
],
),
),
Container(
margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
child: Column(
children: <Widget>[
Text('Manager:', style: TextStyle(color: Colors.black.withOpacity(0.8))),
Text(widget.team.manager, style: Theme.of(context).textTheme.title)
],
),
),
Container(
margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
child: Column(
children: <Widget>[
Text('Captain:', style: TextStyle(color: Colors.black.withOpacity(0.8))),
Text(widget.team.captain, style: Theme.of(context).textTheme.title)
],
),
),
Container(
margin: EdgeInsets.fromLTRB(0, 20, 0, 10),
child: RaisedButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => MapsWidget(widget.team)));
},
color: Colors.blue,
textColor: Colors.white,
padding: EdgeInsets.all(10.0),
child: Column( // Replace with a Row for horizontal icon + text
children: <Widget>[
Icon(Icons.map),
Text("Show Maps")
],
),
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(15.0),
),
),
)
],
)
)
),
),
),
);
}
}
That codes build widgets combination of Container, Card, Column, Image, Text, and RaisedButton. The RaisedButton has onPressed event that action to navigate to the MapsWidget page.
Step #6: Display Google Maps
To display Google Maps in the Flutter app, we need to set up Google Maps iOS and Android in Google Developer Console to get the Google Maps API KEY. We will not cover how to set up Google Maps, you can refer to our other tutorial. To implement Google Maps in Flutter application, we will use google_maps_flutter dependency.
We assume you have already got Google Maps API KEY. Next, open and edit pubspec.yml then add this dependency under flutter dependency.
dependencies:
flutter:
sdk: flutter
google_maps_flutter: ^0.5.11
In Android Studio click Packages get in Flutter commands to download the package.
Or you can type this command in the Terminal to download the package.
flutter packages get
Next, open and edit android/app/src/main/AndroidManifest.xml then add this meta-data under the application line.
<application
android:name="io.flutter.app.FlutterApplication"
android:label="flutter_tutorial_app"
android:icon="@mipmap/ic_launcher">
<meta-data android:name="com.google.android.geo.API_KEY"
android:value="YOUR_GOOGLE_MAPS_API_KEY"/>
...
</application>
Replace YOUR_GOOGLE_MAPS_API_KEY with your valid Google Maps API KEY. Next, open and edit ios/Runner/AppDelegate.m then add this import of GoogleMaps.
#import "GoogleMaps/GoogleMaps.h"
Add GMSService in the didFinishLaunchingWithOptions method.
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Add the following line, with your API key
[GMSServices provideAPIKey: @"YOUR-API-KEY"];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
If you are using Swift, open and edit ios/Runner/AppDelegate.swift then add this import.
import GoogleMaps
Add GMSService in the didFinishLaunchingWithOptions method.
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GMSServices.provideAPIKey("YOUR-API-KEY")
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
Next, open and edit ios/Runner/Info.plist then add these lines under <dict> line.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Add the following entry, from here, -->
<key>io.flutter.embedded_views_preview</key>
<true/>
<!-- to here. -->
...
</dict>
</plist>
Now, we can implement Google Maps to the Flutter widget page. Create a new Dart file to display Google Maps.
touch lib/mapswidget.dart
Open and edit that file then add these imports of Flutter material, team object, and google_maps_flutter.
import 'package:flutter/material.dart';
import 'package:flutter_tutorial_app/team.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
Next, add the MapsWidget class that extends StatefulWidget and contain a constructor with a team object field, Team object variable, and overridden _MapsWidgetState method.
class MapsWidget extends StatefulWidget {
MapsWidget(this.team);
final Team team;
@override
_MapsWidgetState createState() => _MapsWidgetState();
}
Add _MapsWidgetState class that implementing widget building to display Google Maps.
class _MapsWidgetState extends State<MapsWidget> {
_MapsWidgetState();
GoogleMapController mapController;
void _onMapCreated(GoogleMapController controller) {
mapController = controller;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.team.name),
),
body: GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: CameraPosition(
target: LatLng(widget.team.lat, widget.team.lng),
zoom: 11.0,
),
),
);
}
}
As you can see, Google Maps LatLng use data from team object.
Step #7: Run and Test Flutter Apps to Android and iOS
To run this Flutter apps to Android, simply click the play button in the Android Studio toolbar. Make sure your Android device is connected to your computer and appear in Android Studio Toolbar.
Here the working Flutter apps on Android device look like.
To run this Flutter apps to the iOS device, at least you must have Apple Developer personal account with your own domain (our example: com.djamware) as a bundle or package. Next, open the ios/Runner.xcworkspace in XCode then click Runner in the Project Navigator.
In Runner.xcodeproj click the Build Settings tab.
Scroll down and find Signing then choose Development Team to your Apple Developer personal account. Now, you can run the Flutter apps to iOS Device from Xcode or Android Studio. Here are the Flutter apps on iOS devices look like.
That it's, the Flutter Tutorial: Create a Native Android and iOS Apps Quickly. You can get the full source code from our GitHub.
That just the basic. If you need more deep learning about Flutter, Dart, or related you can take the following cheap course:
- Learn Flutter From Scratch
- Flutter, Beginner to Intermediate
- Flutter in 7 Days
- Learn Flutter from scratch
- Dart and Flutter: The Complete Developer's Guide
Thanks!