Magic Lane
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