I am learning to develop a weather app, with MVVM architecture, using Service for API interactions and Provider for state-management. I have implemented a WeatherService class with the fetchWeather() method and a WeatherViewModel class with fetchWeather() method (I am using a json-server to mimic a weather data hosted locally).

WeatherService Class:

class WeatherService {
  final String baseUrl = 'http://localhost:3001';

  Future<Map<String, dynamic>> fetchWeather(String location) async {
    final uri = Uri.parse('$baseUrl/weather');
    final response = await http.get(uri);
    if (kDebugMode) {
      print(response);
    }

    if (response.statusCode == 200) {
      final decodedResponse = jsonDecode(response.body);
      if (decodedResponse != null && decodedResponse['current'] != null) {
        return decodedResponse;
      } else {
        throw Exception('Weather data is incomplete or unavailable');
      }
    } else {
      throw Exception('Failed to fetch weather data: ${response.statusCode}');
    }
  }
}

I am trying to implement a view-model class to handle UI related requests without exposing the API services, so used provider to provide the Classes in the main() using MultiProvider.

WeatherViewModel Class:

class WeatherViewModel extends ChangeNotifier {
  final WeatherService _weatherService;
  late Map<String, dynamic> _weatherData = {}; // Initialize _weatherData
  bool _isLoading = false;
  String _errorMessage = '';

  WeatherViewModel({required WeatherService weatherService})
      : _weatherService = weatherService;

  Map<String, dynamic> get weatherData => _weatherData;
  bool get isLoading => _isLoading;
  String get errorMessage => _errorMessage;

  Future<void> fetchWeather(String location) async {
    _isLoading = true;
    _errorMessage = '';
    notifyListeners();

    try {
      final weather = await _weatherService.fetchWeather(location);
      _weatherData = weather;
    } catch (e) {
      _errorMessage = 'Failed to fetch weather data: $e';
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }
}

As I am a newbie, I am not able to debug or analyze where I am making a mistake in the view-model class that I am not able to get the data in the UI via the WeatherViewModel class.

HomePage() code is below, which executes the else block with 'Temperature data unavailable'

class HomePage extends StatelessWidget {
  final String title;

  const HomePage({super.key, required this.title});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Insta Weather'),
      ),
      body: Center(
        child: Consumer<WeatherViewModel>(
          builder: (context, weatherViewModel, _) {
            if (weatherViewModel.isLoading) {
              return const CircularProgressIndicator();
            } else if (weatherViewModel.errorMessage.isNotEmpty) {
              return Text('Error: ${weatherViewModel.errorMessage}');
            } else if (weatherViewModel.weatherData != null &&
                weatherViewModel.weatherData.containsKey('current') &&
                weatherViewModel.weatherData['current'] != null &&
                weatherViewModel.weatherData['current']
                    .containsKey('temperature')) {
              Text(
                  'Temperature: ${weatherViewModel.weatherData['current']['temperature']}');
              // Display other weather details similarly...
            } else {
              return const Text('Temperature data unavailable');
              // Handle other unavailable data...
            }
            return const Text('No Data');
          },
        ),
      ),
    );
  }
}

Note:

When WeatherService is provided via context, and by implementing a FutureBuilder widget, I am getting the data easily in the UI. Code is below for reference.

@override
  Widget build(BuildContext context) {
    WeatherService weatherService = Provider.of<WeatherService>(context);

    return Scaffold(
      body: FutureBuilder<Map<String, dynamic>>(
        future: weatherService.fetchWeather('New York'),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          } else {
            return SizedBox(
              width: MediaQuery.of(context).size.width,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  Text('Temperature: ${snapshot.data?['location']['name']}'),
                  Text(
                      'Temperature: ${snapshot.data?['current']['temperature']}'),
                ],
              ),
            );
          }
        },
      ),
    );
  }
}

Mock Json Data Sample:

{
  "request": {
    "type": "City",
    "query": "New York, United States of America",   
  },
  "location": {
    "name": "New York",
    "country": "United States of America",
    "region": "New York",    
  },
  "current": {
    "observation_time": "12:14 PM",
    "temperature": 13,
    "visibility": 16
  }
}

I will be really grateful if anyone can point me in the right direction as I have spent almost 2 weeks in this issue. Thanks in advance.

0

There are 0 best solutions below