Home > Software engineering >  How to make an async http request at app startup and save the value inside widget in Flutter
How to make an async http request at app startup and save the value inside widget in Flutter

Time:10-31

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.

  1. 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.

  2. 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 by Text(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.

  • Related