Weather App
Pada tutorial ini kita akan membuat sebuah aplikasi untuk menampilkan cuaca yang datanya diambil dari website cuaca menggunakan API.
Register API
Sebelum memulai membuat project, kita harus mendaftar dahulu di https://openweathermap.org/ untuk mendapatkan akses ke API cuaca dari openweathermap.
Project Clone
Clone project yang ada di alamat git berikut ini https://github.com/yest/weather.
Geolocator Package
Untuk mendapatkan data cuaca yang sesuai dengan lokasi kita, kita memerlukan sebuah package untuk mendapatkan lokasi saat ini. Package yang digunakan adalah geolocator yang dapat diakses di https://pub.dev/packages/geolocator. Tambahkan package tersebut ke pubspec.yaml.
Update Permission and SDK
Agar aplikasi kita dapat menggunakan GPS, kita harus menambahkan permission di AndroidManifest.xml. Buka file AndroidManifest.xml yang ada didalam folder android/app/src/main, kemudian tambahkan permission berikut ini dibawah tag <manifest> dan sebelum tag <application>.
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
Package Geolocator membutuhkan versi SDK 33, untuk itu buka file android/app/build.gradle dan ubah
menjadi
Get Current Location
Pada file services/location.dart tambahkan kode berikut untuk mendapatkan lokasi saat ini.
import 'package:geolocator/geolocator.dart';
class Location {
double latitude = 0.0;
double longitude = 0.0;
final LocationSettings locationSettings = LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 100,
);
Future<void> getCurrentLocation() async {
bool serviceEnabled;
LocationPermission permission;
// Test if location services are enabled.
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
// Location services are not enabled don't continue
// accessing the position and request users of the
// App to enable the location services.
return Future.error('Location services are disabled.');
}
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
// Permissions are denied, next time you could try
// requesting permissions again (this is also where
// Android's shouldShowRequestPermissionRationale
// returned true. According to Android guidelines
// your App should show an explanatory UI now.
return Future.error('Location permissions are denied');
}
}
if (permission == LocationPermission.deniedForever) {
// Permissions are denied forever, handle appropriately.
return Future.error(
'Location permissions are permanently denied, we cannot request permissions.');
}
// When we reach here, permissions are granted and we can
// continue accessing the position of the device.
Position position = await Geolocator.getCurrentPosition(
locationSettings: locationSettings);
latitude = position.latitude;
longitude = position.longitude;
}
}
Karena proses pengambilan lokasi membutuhkan waktu, maka method getCurrentLocation() menggunakan teknik pemrograman asinkron, yang artinya proses lainnya dapat berjalan tanpa harus menunggu proses getCurrentLocation() selesai.
Pada file screens/loading_screen.dart import file services/location.dart.
Agar aplikasi dapat mengambil lokasi saat ini secara otomatis saat aplikasi dijalankan, kita buat sebuah method initState(). Method ini akan otomatis dijalankan saat sebuah state diciptakan dan hanya dijalankan satu kali.
class _LoadingScreenState extends State<LoadingScreen> {
@override
void initState() {
super.initState();
getLocation();
}
void getLocation() async {
Location location = Location();
await location.getCurrentLocation();
print(location.latitude);
print(location.longitude);
}
@override
Widget build(BuildContext context) {
return Scaffold();
}
}
Saat kita hot restart aplikasi, maka di DEBUG CONSOLE akan terlihat lokasi latilude dan longitude saat ini.

OpenWeather API
Untuk mengambil data cuaca dari openweathermap.org, kita bisa melakukan get request dengan format seperti berikut
https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={API key}
Terdapat tiga parameter yang dibutuhkan yaitu lat (latitude), lon (longitude), dan appid. Untuk mengetahui API key, login ke openweathermap.org dan masuk ke menu API keys. Untuk mencoba apakah API keys sudah aktif, salin latitude, longitude dari aplikasi flutter dan API key dari openweathermap.org, kemudian masukkan kedalam link get request dan buka link tersebut melalui web browser.
Contoh:
https://api.openweathermap.org/data/2.5/weather?lat=37.42342342342342&lon=-122.08395287867832&appid=12345
Jika API key benar dan sudah aktif, maka akan ada respon dalam bentuk JSON data cuaca saat ini.

Untuk memudahkan pemahaman hasil respon, install extension JSON Viewer Pro di Google Chrome. Berikut hasil setelah penggunaan extension.

Android Emulator Location Setting
Secara default, Android emulator akan menggunakan lokasi latitude 37.42342342342342 dan longitude -122.08395287867832 yang merupakan lokasi Mountain View California, Amerika Serikat. Untuk mengubah lokasi sesuai yang kita mau, ikuti langkah-langkah berikut ini:
-
Klik tombol tiga titik yang ada di kanan bawah emulator untuk membuka window setting.

-
Ketikan alamat yang diinginkan, contoh: Surakarta. Kemudian tekan tombol
SAVE POINT
-
Beri nama sesuai kengininan dan tekan tombol
OK
-
Klik tombol
SET LOCATION
-
Tutup window setting dan buka aplikasi Maps kemudian klik tombol lokasi saat ini

-
Jika lokasi di aplikasi Maps sudah sesuai dengan lokasi yang kita setting, tutup aplikasi Maps dan hot restart aplikasi weather.
Get Weather Data
Untuk dapat mengakses API dari Flutter, kita membutuhkan package http yang dapat diperoleh di https://pub.dev/packages/http. Tambahkan package tersebut ke pubspec.yaml.
Berikutnya kita buat sebuah class untuk mengakses API dan mendecode respon JSON. Pada file services/networking.dart ketikkan kode berikut ini.
import 'package:http/http.dart' as http;
import 'dart:convert';
class NetworkHelper {
NetworkHelper({required this.url});
final String url;
Future getData() async {
http.Response response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
String data = response.body;
return jsonDecode(data);
} else {
print(response.statusCode);
}
}
}
Pada kode diatas, kita import package http yang digunakan untuk mengakses API dan package dart:convert yang digunakan untuk mendecode JSON. Fungsi getData() menggunakan teknik asinkron karena membutuhkan waktu untuk mengambil data dari API.
Kemudian pada file screens/loading_screens.dart ubah method getLocation() menjadi getLocationData() dan sesuaikan kodenya seperti berikut ini.
import 'package:flutter/material.dart';
import 'package:weather/services/location.dart';
import 'package:weather/services/networking.dart';
import 'location_screen.dart';
const apiKey = 'ISI DENGAN API KEY';
class LoadingScreen extends StatefulWidget {
const LoadingScreen({Key? key}) : super(key: key);
@override
State<LoadingScreen> createState() => _LoadingScreenState();
}
class _LoadingScreenState extends State<LoadingScreen> {
double latitude = 0.0;
double longitude = 0.0;
@override
void initState() {
super.initState();
getLocationData();
}
void getLocationData() async {
Location location = Location();
await location.getCurrentLocation();
latitude = location.latitude;
longitude = location.longitude;
NetworkHelper networkHelper = NetworkHelper(
url:
'https://api.openweathermap.org/data/2.5/weather?lat=$latitude&lon=$longitude&appid=$apiKey',
);
var weatherData = await networkHelper.getData();
if (!mounted) return;
Navigator.push(context,
MaterialPageRoute(builder: (context) => const LocationScreen()));
}
@override
Widget build(BuildContext context) {
return Scaffold();
}
}
Saat kita hot restart, maka akan tampil halaman LoadingScreen sebentar dan kemudian berpindah ke halaman LocationScreen. Karna di halaman LoadingScreen hanya berisi sebuah widget Scaffold kosong, maka hanya terlihat halaman hitam saja. Jika proses pengambilan data dari API memakan waktu yang lama misalkan karena internetnya lambat, maka pengguna akan mengira aplikasinya tidak berfungsi karna hanya menampilkan halaman hitam saja. Untuk itu kita perlu membuat sebuah indikator agar pengguna tahu bahwa aplikasi sedang menggambil data, atau yang biasa disebut sebagai loading indicator.
Loading Indicator
Package yang kita gunakan untuk loading indikator adalah Flutter Spinkit yang dapat diakses di https://pub.dev/packages/flutter_spinkit. Tambahkan package tersebut ke pubspec.yaml.
Pada file screens/loading_screens.dart import package Flutter Spinkit dan sesuaikan kode method build untuk menampilkan loading indikator.
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: SpinKitFoldingCube(
color: Colors.white,
),
),
);
}
Saat kita hot restart aplikasi, maka akan terlihat sebuah loading indicator sebelum masuk ke halaman LocationScreen.
Passing the Data
Kita mengambil data lokasi dan cuaca pada halaman LoadingScreen, namun kita ingin menampilkan informasi cuaca pada halaman LocationScreen. Untuk itu kita harus mengirimkan data dari LoadingScreen ke LocationScreen.
Pada class LocationScreen kita buat sebuah properti dengan nama locationWeather yang akan menyimpan data cuaca, dan sebuah constructor yang akan memasukkan data ke properti locationWeather.
class LocationScreen extends StatefulWidget {
const LocationScreen({required this.locationWeather, Key? key})
: super(key: key);
final dynamic locationWeather;
@override
State<LocationScreen> createState() => _LocationScreenState();
}
Kemudian pada halaman LoadingScreen, tambahkan parameter weatherData saat Navigator.push ke halaman LocationScreen.
var weatherData = await networkHelper.getData();
if (!mounted) return;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LocationScreen(
locationWeather: weatherData,
),
),
);
Untuk mengakses properti locationWeather dari class _LocationScreenState kita dapat menggunakan properti widget.
Update UI
Pada class _LocationScreenState tambahkan beberapa properti, sebuah objek weatherModel, dan sebuah method updateUI. Kemudian buat sebuah method initState() yang didalamnya memanggil method updateUI dengan parameter data cuaca dari class LocationScreen. Sebelumnya import file services/weather.dart terlebih dahulu.
class _LocationScreenState extends State<LocationScreen> {
int temperature = 0;
String weatherIcon = '';
String message = '';
String cityName = '';
WeatherModel weatherModel = WeatherModel();
@override
void initState() {
super.initState();
updateUI(widget.locationWeather);
}
void updateUI(dynamic weatherData) {
setState(() {
if (weatherData == null) {
temperature = 0;
weatherIcon = "Error";
message = "Unable to get weather data";
cityName = '';
return;
}
temperature = weatherData['main']['temp'].toInt();
cityName = weatherData['name'];
var weatherId = weatherData['weather'][0]['id'];
weatherIcon = weatherModel.getWeatherIcon(weatherId);
message = weatherModel.getMessage(temperature);
});
}
...
Kemudian ubah widget-widget yang menampilkan suhu, icon dan pesan dengan variabel yang sesuai.
Padding(
padding: EdgeInsets.only(left: 15.0),
child: Row(
children: <Widget>[
Text(
'$temperatureยฐ',
style: kTempTextStyle,
),
Text(
weatherIcon,
style: kConditionTextStyle,
),
],
),
),
Padding(
padding: EdgeInsets.only(right: 15.0),
child: Text(
"$message in $cityName!",
textAlign: TextAlign.right,
style: kMessageTextStyle,
),
),
Hot restart aplikasi. Hasilnya akan terlihat seperti berikut ini.

Jika kita perhatikan suhu menunjukkan angka 283 dalam satuan kelvin bukan celcius. Untuk mengubahnya kita bisa melakukan konversi dengan rumus 283 - 273.15. Namun kita dapat menggunakan cara yang lebih mudah yaitu kita tentukan satuan yang kita inginkan saat request API ke openweathermap.org.
https://api.openweathermap.org/data/2.5/weather?lat=$latitude&lon=$longitude&appid=$apiKey&units=metric
Hasil

Refactor the Code
Agar pengambilan data lokasi dan cuaca dapat dilakukan dari berbagai halaman, maka kita pindahkan kode-kode untuk pengambilan lokasi dan cuaca ke file services/weather.dart.
Ubah file services/weather.dart seperti berikut ini.
import 'package:weather/services/location.dart';
import 'package:weather/services/networking.dart';
const apiKey = 'ISI DENGAN API KEY';
class WeatherModel {
Future<dynamic> getCityWeather(String cityName) async {
NetworkHelper networkHelper = NetworkHelper(
url:
'https://api.openweathermap.org/data/2.5/weather?q=$cityName&appid=$apiKey&units=metric',
);
var weatherData = await networkHelper.getData();
return weatherData;
}
Future<dynamic> getLocationWeather() async {
Location location = Location();
await location.getCurrentLocation();
NetworkHelper networkHelper = NetworkHelper(
url:
'https://api.openweathermap.org/data/2.5/weather?lat=${location.latitude}&lon=${location.longitude}&appid=$apiKey&units=metric',
);
var weatherData = await networkHelper.getData();
return weatherData;
}
String getWeatherIcon(int condition) {
if (condition < 300) {
return '๐ฉ';
} else if (condition < 400) {
return '๐ง';
} else if (condition < 600) {
return 'โ๏ธ';
} else if (condition < 700) {
return 'โ๏ธ';
} else if (condition < 800) {
return '๐ซ';
} else if (condition == 800) {
return 'โ๏ธ';
} else if (condition <= 804) {
return 'โ๏ธ';
} else {
return '๐คทโ';
}
}
String getMessage(int temp) {
if (temp > 25) {
return 'It\'s ๐ฆ time';
} else if (temp > 20) {
return 'Time for shorts and ๐';
} else if (temp < 10) {
return 'You\'ll need ๐งฃ and ๐งค';
} else {
return 'Bring a ๐งฅ just in case';
}
}
}
Kemudian ubah method getLocationData() pada halaman LoadingScreen menjadi seperti berikut ini. Sebelumnya, import file services/weather.dart terlebih dahulu.
import 'package:weather/services/weather.dart';
...
void getLocationData() async {
var weatherData = await WeatherModel().getLocationWeather();
if (!mounted) return;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LocationScreen(
locationWeather: weatherData,
),
),
);
}
Update New Location
Pada icon near_me di sebelah kiri atas, tambahkan kode berikut pada method onPressed().
TextButton(
onPressed: () async {
var weatherData =
await WeatherModel().getLocationWeather();
updateUI(weatherData);
},
child: Icon(
Icons.near_me,
size: 50.0,
),
),
Get Weather by City Name
Sesuaikan file screens/city_screens.dart seperti berikut ini.
import 'package:flutter/material.dart';
import 'package:weather/utilities/constants.dart';
class CityScreen extends StatefulWidget {
const CityScreen({Key? key}) : super(key: key);
@override
State<CityScreen> createState() => _CityScreenState();
}
class _CityScreenState extends State<CityScreen> {
String cityName = '';
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('images/city_background.jpg'),
fit: BoxFit.cover,
),
),
constraints: BoxConstraints.expand(),
child: SafeArea(
child: Column(
children: <Widget>[
Align(
alignment: Alignment.topLeft,
child: TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Icon(
Icons.arrow_back_ios,
size: 50.0,
),
),
),
Container(
padding: EdgeInsets.all(20.0),
child: TextField(
style: const TextStyle(
color: Colors.black,
),
decoration: const InputDecoration(
filled: true,
fillColor: Colors.white,
icon: Icon(Icons.location_city, color: Colors.white),
hintText: 'Enter city name',
hintStyle: TextStyle(
color: Colors.grey,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
borderSide: BorderSide.none,
),
),
onChanged: (value) {
cityName = value;
},
),
),
TextButton(
onPressed: () {
Navigator.pop(context, cityName);
},
child: Text(
'Get Weather',
style: kButtonTextStyle,
),
),
],
),
),
),
);
}
}
Kemudian pada halaman LocationScreen ubah kode widget icon location_city seperti berikut.
TextButton(
onPressed: () async {
var typedName = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CityScreen(),
),
);
if (typedName != null) {
var weatherData =
await WeatherModel().getCityWeather(typedName);
updateUI(weatherData);
}
},
child: Icon(
Icons.location_city,
size: 50.0,
),
),
Hot restart aplikasi.