Weather Forecast¶
This example demonstrates how to create a Flutter application that utilizes the gem_kit
package to display a weather forecast on a map. The application initializes the GemKit SDK and provides a user interface to show current, hourly, and daily weather forecasts.
Setup¶
Prerequisites¶
Build and Run¶
Navigate to the project folder for this example to build and run the application.
Note - the gem_kit
directory containing the Maps SDK for Flutter
should be in the plugins
directory of the example, e.g.
example_pathname/plugins/gem_kit
- see the environment setup guide above.
Run: flutter pub get
Configure the native parts:
First, verify that the ANDROID_SDK_ROOT
environment variable
is set to the root path of your android SDK.
In android/build.gradle
add the maven
block as shown,
within the allprojects
block, for both debug and release builds:
allprojects {
repositories {
google()
mavenCentral()
maven {
url "${rootDir}/../plugins/gem_kit/android/build"
}
}
}
in android/app/build.gradle
within the android
block, in the defaultConfig
block,
the android SDK version minSdk
must be set as shown below.
Additionally, for release builds, in android/app/build.gradle
,
within the android
block, add the buildTypes
block as shown:
Replace example_pathname
with the actual project pathname
android {
defaultConfig {
applicationId "com.magiclane.gem_kit.examples.example_pathname"
minSdk 21
targetSdk flutter.targetSdk
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
minifyEnabled false
shrinkResources false
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
Then run the project:
flutter run --debug
orflutter run --release
App entry and initialization¶
const projectApiToken = String.fromEnvironment('GEM_TOKEN');
void main() {
runApp(const MyApp());
}
This code initializes the projectApiToken with the required authorization token and launches the app.
How It Works¶
Main App Setup
: The main app initializes GemKit and displays a map.Weather Forecast Page
: Users can navigate through current, hourly and daily forecasts for a specific location.
User Interface¶
The main application consists of a simple user interface that displays a map along with a weather forecast option. The user can tap on the weather icon in the app bar to navigate to a detailed weather forecast page.
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Weather Forecast',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
void dispose() {
GemKit.release();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.deepPurple[900],
title: const Text('Weather Forecast', style: TextStyle(color: Colors.white)),
actions: [
IconButton(
onPressed: () => _onWeatherForecastTap(context),
icon: Icon(
Icons.sunny,
color: Colors.white,
),
),
],
),
body: GemMap(appAuthorization: projectApiToken),
);
}
void _onWeatherForecastTap(BuildContext context) {
Navigator.of(context).push(MaterialPageRoute<dynamic>(
builder: (context) => WeatherForecastPage(),
));
}
}
This code sets up the main application UI, including an app bar and a body that contains a map.
Weather Forecast Page¶
The WeatherForecastPage
displays the weather forecast options, allowing users to switch between current, hourly, and daily forecasts. Below is the implementation of the WeatherForecastPage.
class WeatherForecastPage extends StatefulWidget {
const WeatherForecastPage({super.key});
@override
State<WeatherForecastPage> createState() => _WeatherForecastPageState();
}
class _WeatherForecastPageState extends State<WeatherForecastPage> {
WeatherTab _weatherTab = WeatherTab.now;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
foregroundColor: Colors.white,
title: const Text("Weather Forecast", style: TextStyle(color: Colors.white)),
backgroundColor: Colors.deepPurple[900],
),
body: Padding(
padding: const EdgeInsets.all(15.0),
child: Column(
children: [
// Tab buttons for 'Now', 'Hourly', and 'Daily' forecasts
SizedBox(
height: 40.0,
child: Row(
children: [
Expanded(
child: InkWell(
child: Center(child: Text("Now")),
onTap: () => setState(() {
_weatherTab = WeatherTab.now;
}),
),
),
Expanded(
child: InkWell(
child: Center(child: Text("Hourly")),
onTap: () => setState(() {
_weatherTab = WeatherTab.hourly;
}),
),
),
Expanded(
child: InkWell(
child: Center(child: Text("Daily")),
onTap: () => setState(() {
_weatherTab = WeatherTab.daily;
}),
),
),
],
),
),
// Display the selected forecast page
Expanded(
child: Builder(
builder: (context) {
if (_weatherTab == WeatherTab.now) {
return _buildCurrentForecast();
} else if (_weatherTab == WeatherTab.hourly) {
return _buildHourlyForecast();
} else if (_weatherTab == WeatherTab.daily) {
return _buildDailyForecast();
}
return Container();
},
),
),
],
),
),
);
}
FutureBuilder<LocationForecast> _buildCurrentForecast() {
return FutureBuilder(
future: _getCurrentForecast(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (!snapshot.hasData) return Center(child: Text("Error loading current forecast."));
return ForecastNowPage(condition: snapshot.data!, landmarkName: "Paris");
},
);
}
FutureBuilder<List<LocationForecast>> _buildHourlyForecast() {
return FutureBuilder(
future: _getHourlyForecast(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (!snapshot.hasData) return Center(child: Text("Error loading hourly forecast."));
return ForecastHourlyPage(locationForecasts: snapshot.data!);
},
);
}
FutureBuilder<List<LocationForecast>> _buildDailyForecast() {
return FutureBuilder(
future: _getDailyForecast(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (!snapshot.hasData) return Center(child: Text("Error loading daily forecast."));
return ForecastDailyPage(locationForecasts: snapshot.data!);
},
);
}
}
This code implements the WeatherForecastPage
, which manages the state of the currently selected weather tab and displays the appropriate forecast information.
Getting Current, Hourly and Daily Forecasts¶
The following methods retrieve hourly and daily forecasts from the GemKit SDK.
Future<LocationForecast> _getCurrentForecast() async {
final locationCoordinates = Coordinates(latitude: 48.864716, longitude: 2.349014);
final weatherCurrentCompleter = Completer<List<LocationForecast>?>();
WeatherService.getCurrent(
coords: [locationCoordinates],
onCompleteCallback: (err, result) async {
weatherCurrentCompleter.complete(result);
});
final currentForecast = await weatherCurrentCompleter.future;
return currentForecast!.first;
}
Future<List<LocationForecast>> _getHourlyForecast() async {
final locationCoordinates = Coordinates(latitude: 48.864716, longitude: 2.349014);
final weatherHourlyCompleter = Completer<List<LocationForecast>?>();
WeatherService.getHourlyForecast(
hours: 24,
coords: [locationCoordinates],
onCompleteCallback: (err, result) async {
weatherHourlyCompleter.complete(result);
},
);
final currentForecast = await weatherHourlyCompleter.future;
return currentForecast!;
}
Future<List<LocationForecast>> _getDailyForecast() async {
final locationCoordinates = Coordinates(latitude: 48.864716, longitude: 2.349014);
final weatherDailyCompleter = Completer<List<LocationForecast>?>();
WeatherService.getDailyForecast(
days: 10,
coords: [locationCoordinates],
onCompleteCallback: (err, result) async {
weatherDailyCompleter.complete(result);
},
);
final currentForecast = await weatherDailyCompleter.future;
return currentForecast!;
}
These methods use the WeatherService
to fetch current, hourly and daily weather data for a specified location.
Hourly Forecast Page¶
The ForecastHourlyPage
displays the hourly weather forecasts. Below is the implementation of this page.
class ForecastHourlyPage extends StatelessWidget {
final List<LocationForecast> locationForecasts;
const ForecastHourlyPage({super.key, required this.locationForecasts});
@override
Widget build(BuildContext context) {
return SizedBox(
height: MediaQuery.of(context).size.height * 0.8,
child: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: locationForecasts.length,
itemBuilder: (context, index) {
return ListTile(
title: Text("Hour ${index + 1}: ${locationForecasts[index].temperature} °C"),
subtitle: Text(locationForecasts[index].condition),
);
},
),
),
],
),
);
}
}
This code defines the ForecastHourlyPage
, which presents a list of hourly weather forecasts for the user.
Daily Forecast Page¶
The ForecastDailyPage
displays the daily weather forecasts. Below is the implementation of this page.
class ForecastDailyPage extends StatelessWidget {
final List<LocationForecast> locationForecasts;
const ForecastDailyPage({super.key, required this.locationForecasts});
@override
Widget build(BuildContext context) {
return SizedBox(
height: MediaQuery.of(context).size.height * 0.8,
child: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: locationForecasts.length,
itemBuilder: (context, index) {
return ListTile(
title: Text("Day ${index + 1}: ${locationForecasts[index].temperature} °C"),
subtitle: Text(locationForecasts[index].condition),
);
},
),
),
],
),
);
}
}
This code defines the ForecastDailyPage
, which presents a list of daily weather forecasts for the user.