Skip to content

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.

truck_profile - main screen

Setup

First, get an API key token, see the Getting Started guide.

Prerequisites

Make sure you completed the Environment Setup - Flutter Examples guide before starting this guide.

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
        }
    }
}

In the ios/Podfile configuration file, at the top, set the minimum ios platform version to 14 like this:

platform :ios, '14.0'

We recommend you to run these commands after you copy the gem_kit into your project: |flutter clean |flutter pub get |and |cd ios |pod install

Then run the project:

flutter run --debug
or
flutter 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.

truck_profile - main screen

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.

truck_profile - main screen

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.

truck_profile - main screen

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.

truck_profile - main screen

Flutter Examples

Maps SDK for Flutter Examples can be downloaded or cloned with Git