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.