Search code examples
flutterflutter-state

Widget builded with null value from api call in initState


I'm having trouble displaying the result of api call in widget. I'm getting "Unexpected null value" error. From some reasons I don't want to use FutureBuilder in this case. With FutureBuilder everything is ok.

class Album {
  final int userId;
  final int id;
  final String title;

  const Album({
    required this.userId,
    required this.id,
    required this.title,
  });

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
    );
  }
}

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Album ? myAlbum;
  Future<void>  fetchAlbum() async {
    final response = await http
        .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));

    if (response.statusCode == 200) {
      // If the server did return a 200 OK response,
      // then parse the JSON.
      myAlbum= Album.fromJson(jsonDecode(response.body));
    } else {
      // If the server did not return a 200 OK response,
      // then throw an exception.
      throw Exception('Failed to load album');
    }
  }
  @override
  void initState() {
    fetchAlbum();
    super.initState();
  }
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fetch Data Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Fetch Data Example'),
        ),
        body: Center(
          child: Text(myAlbum!.title)
        ),
      ),
    );
  }
}

I assume that problem is because widget is builded before initState call.


Solution

  • This happens because your API Calls are Asynchronous. This means they take time in order to complete. Your request goes to the server, the server answers, it goes back and only then you will have the response value.

    What makes it clear something is async is the keyword await before your http.get request. As the word suggests, you have to await for it to finish in order to continue the rest of the method.

    This is called asynchronous because it happens on the background. Dart has a single thread execution model. If the execution were to stop and wait synchronously for the response, your app would freeze in the meanwhile.

    The way your application is written does not tell how to behave while you don't have your response value. There are many ways to do this. One of them, is to display a default value while the response isn't ready:

    child: Text(myAlbum?.title ?? 'Default title.')
    

    This ?? is a null-checking operator. It return the right value if the left one is null.

    In your example, you should also need a setState((){}); inside your fetchAlbum method, in order to let the UI know it needs to rebuild the screen.

    (When you hot reload, it rebuilds the screen without changing the state. So if you hot reload after the API call is finished, you would not get the same error)