Published on

Building a Feature-Rich Weather App with Flutter: A Deep Dive

Authors

Building a Feature-Rich Weather App with Flutter: A Deep Dive

Table of Contents

Introduction

In this post, I'll take you through the journey of creating a feature-rich weather application using Flutter. This app not only fetches real-time weather data but also provides a polished, user-friendly interface for displaying it. Let's explore the key features, technical implementation, challenges faced, and lessons learned during this project.

Key Features

  1. Current Location Weather: The app starts by requesting the user's current location and displaying the local weather.
  2. City Search: Users can search for weather information in any city worldwide.
  3. Detailed Weather Information: The app displays temperature, weather description, humidity, and wind speed.
  4. Autocomplete Search: As users type a city name, the app suggests matching cities.
  5. External Link: Each weather result includes a link to more detailed information on OpenWeatherMap.

Technical Implementation

API Integration

We used the OpenWeatherMap API to fetch weather data. Here's how we make API calls:

Future<void> getWeather() async {
  if (city.isNotEmpty) {
    final url = Uri.parse('https://api.openweathermap.org/data/2.5/weather?q=$city&appid=$apiKey&units=metric');
    final response = await http.get(url);

    if (response.statusCode == 200) {
      final json = jsonDecode(response.body);
      weather = Weather.fromJson(json);
    } else {
      throw Exception('Failed to load weather data');
    }
  }
}

Location Services

To get the user's current location, we used the geolocator package:

Position position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.low);
await getWeatherByCoordinates(position.latitude, position.longitude);

UI Design

We aimed for a clean, intuitive interface. Here's our main weather display structure:

Widget buildWeather(Weather weather) => Card(
  elevation: 4,
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(16),
  ),
  child: Padding(
    padding: const EdgeInsets.all(16.0),
    child: Column(
      children: [
        Text(weather.cityName, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
        Text('${weather.temperature.toStringAsFixed(1)} °C', style: TextStyle(fontSize: 40)),
        Text(weather.description),
        Text('Humidity: ${weather.humidity}%'),
        Text('Wind Speed: ${weather.windSpeed} m/s'),
      ],
    ),
  ),
);

Challenges and Solutions

1. Implementing Autocomplete

One of the main challenges was implementing the autocomplete feature for city search. We solved this by using the Autocomplete widget and creating a separate API call to fetch city suggestions:

Autocomplete<String>(
  optionsBuilder: (TextEditingValue textEditingValue) {
    if (textEditingValue.text == '') {
      return const Iterable<String>.empty();
    }
    return getSuggestions(textEditingValue.text);
  },
  onSelected: (String selection) {
    setState(() {
      city = selection.split(',')[0];
    });
    getWeather();
  },
  // ... more configuration ...
)

2. Handling Location Permissions

Another challenge was properly handling location permissions. We implemented a robust permission request system:

Future<bool> _handleLocationPermission() async {
  bool serviceEnabled;
  LocationPermission permission;

  serviceEnabled = await Geolocator.isLocationServiceEnabled();
  if (!serviceEnabled) {
    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
        content: Text('Location services are disabled. Please enable the services')));
    return false;
  }
  permission = await Geolocator.checkPermission();
  if (permission == LocationPermission.denied) {
    permission = await Geolocator.requestPermission();
    if (permission == LocationPermission.denied) {
      ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Location permissions are denied')));
      return false;
    }
  }
  if (permission == LocationPermission.deniedForever) {
    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
        content: Text('Location permissions are permanently denied, we cannot request permissions.')));
    return false;
  }
  return true;
}

Lessons Learned and Best Practices

  1. Error Handling: Implement comprehensive error handling to provide a smooth user experience, even when things go wrong.

  2. API Key Security: Never hardcode API keys in your app. Use environment variables or secure storage solutions.

  3. State Management: For this app, we used setState() for simplicity. For larger apps, consider using more robust state management solutions like Provider or Bloc.

  4. Code Organization: Keep your code modular and well-organized. We separated our Weather model into a separate file for better maintainability.

  5. UI Responsiveness: Always provide visual feedback during loading operations to keep users informed.

Setting Up and Running the Project

To run this project:

  1. Clone the repository:
git clone https://github.com/ibsanju/flutter_weather_app.git 
  1. Navigate to the project directory:
cd flutter_weather_app
  1. Install dependencies:
flutter pub get
  1. Create a file named api_key.dart in the lib folder and add your OpenWeatherMap API key:
    const String apiKey = 'YOUR_API_KEY_HERE';
    
  2. Run the app: flutter run

Make sure you have Flutter installed and set up on your machine before running these commands.

Performance Optimizations

  1. Debouncing API Calls: We implemented debouncing for the autocomplete feature to reduce unnecessary API calls:
Timer? _debounce;

void onSearchChanged(String query) {
  if (_debounce?.isActive ?? false) _debounce!.cancel();
  _debounce = Timer(const Duration(milliseconds: 500), () {
    getSuggestions(query);
  });
}
  1. Caching: We implemented a simple caching mechanism to store recent weather data, reducing API calls for frequently searched cities.

Future Improvements

In future iterations, we could add features like:

  • Weather forecasts for the next few days
  • More detailed weather information (e.g., UV index, air quality)
  • Weather alerts for severe conditions
  • Ability to save favorite locations

Conclusion

Building this weather app with Flutter was an exciting and educational journey. It showcases how Flutter can be used to create cross-platform apps with rich UIs and complex functionalities. The app demonstrates key mobile development concepts like API integration, location services, and autocomplete search.

The process of building this app has not only improved my Flutter skills but also deepened my understanding of mobile app development best practices. I hope this detailed walkthrough helps you in your Flutter journey!

Happy coding!