I need to make a http request at application startup and save the value in the widget, to use it in the child widgets, but I can't make it work. I am using InitState() to make the request, but I can't save and use the value.
This is my code for now. The error I'm getting is that WeatherObject as a parameter to MainWeatherCardWidget can't be null, and I agree, but if I understood things correctly, shouldn't the await keyboard inside fetchData() waits until the http request is completed? Or I misunderstood something?
The variable being optional may be the key, but I need to make it as I need to save the value in it after its initialization, and I can't return something from InitState().
I think it might work with Provider package, but I didn't want to use it in this project, as I don't think a global state is required. Anyway, how to achieve it?
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
CurrentWeatherInterface? weatherObject;
Future<void> fetchData() async {
weatherObject = await fetchCurrentWeather(dotenv.env['API_KEY']);
}
@override
void initState() {
super.initState();
// fetch weather data
fetchData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize:
Size.fromHeight(MediaQuery.of(context).size.height * 0.075),
child: AppBar(
backgroundColor: Colors.transparent,
elevation: 0.0,
),
),
drawer: const Drawer(),
backgroundColor: const Color.fromRGBO(62, 149, 250, 1),
body: SingleChildScrollView(
child: Center(
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.85,
child: Column(
children: [
MainWeatherCardWidget(
weatherObject: weatherObject!,
),
const HourlyWeatherCardListWidget(),
const DailyWeatherListCardWidget(),
],
),
),
),
),
);
}
}
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:weather_app/interfaces/current_weather_interface.dart';
Future<CurrentWeatherInterface> fetchCurrentWeather(String? APIKey) async {
const url =
'http://api.weatherapi.com/v1/current.json?key=729fc1d266eb46589bf122819222505&q=Ararangua&qi=no';
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
return CurrentWeatherInterface.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to fetch data');
}
}
CodePudding user response:
You are correct that the await keyword would make the http fetch be awaited until completed. But the method call to fetchData in your initState is not awaited. So build will be run when initState is completed, without waiting for fetchData. Thereby the value will still be null, and you get the error.
Further, you cannot await it in initState because that is not asynchronous. So have a look at the widget named FutureBuilder
and use that instead with fetchData as the future parameter.
So use FutureBuilder
with fetchData
and MainWeatherCardWidget
CodePudding user response:
Calling await
in initstate
it is not allowed you need to use FutureBuilder
like this:
FutureBuilder<CurrentWeatherInterface>(
future: fetchData(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
weatherObject = snapshot.data!;
return SingleChildScrollView(
child: Center(
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.85,
child: Column(
children: [
MainWeatherCardWidget(
weatherObject: weatherObject!,
),
const HourlyWeatherCardListWidget(),
const DailyWeatherListCardWidget(),
],
),
),
),
);
}
}
},
)
CodePudding user response:
You are using null check (!) on weatherObject while building the widget MainWeatherCardWidget. But, weatherObject gets initialised only after fetchData() is executed. Until that point, weatherObject is nothing but null. That is what causes the null error.
And the usage of await inside fetchData() definition is misplaced. It will only make the code inside fetchData() function wait before proceeding further. But fetchData() itself is called inside initState() asynchronously. So, await inside fetchData() will not do much help when it comes to the time of building the widgets.
Now, to solve this, there are two options.
Initialise the weatherObject: For that, Define a constructor for CurrentWeatherInterface, if it doesn't have one already. And use
CurrentWeatherInterface weatherObject = CurrentWeatherInterface()
instead of simply declaring weatherObject.That way, the widget will have a proper non-null object for building the widget. This method will be easier to implement. Lesser lines of code.
Let the widget properly handle null: For that, change the definition of MainWeatherCardWidget in such a way that, the parameter could be null. And inside its definition, wherever that parameter is used, make arrangements for how to handle the null case. For example,
Text(weatherObject.placeName)
must be replaced byText(weatherObject == null ? "loading" : weatherObject.placeName)
This method will be better if you want to give indications of loading, in the UI.
Also, make sure to wrap the code inside fetchData() with setState.