Home > Software design >  Flutter/Dart: Field 'futureAlbum' has not been initialized
Flutter/Dart: Field 'futureAlbum' has not been initialized

Time:10-13

model_album.dart

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

  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'],
    );
  }
}

http_controller.dart


import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'dart:async';
import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:feed/models/model_album.dart';

class HttpController extends GetxController {

  late Future<Album> futureAlbum;

  @override
  void onInit() {
    super.onInit;
    futureAlbum = fetchAlbum();
  }

  Future<Album> 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.
      debugPrint('futureAlbum initialized');
      return 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');
    }
  }
}

view_http.dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'package:feed/views/view_http.dart';
import 'package:feed/models/model_album.dart';
import 'package:feed/controllers/http_controller.dart';

class ViewHttp extends StatelessWidget {

  var httpController = HttpController();

  @override
  Widget build(BuildContext context) {
    return Center(
        child: FutureBuilder<Album>(
      future: httpController.futureAlbum,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Text(snapshot.data!.title);
        } else if (snapshot.hasError) {
          return Text('${snapshot.error}');
        }

        // By default, show a loading spinner.
        return const CircularProgressIndicator();
      },
    ));
  }
}

I am trying to learn how to use Getx and Network requests. I took this code from: https://flutter.dev/docs/cookbook/networking/fetch-data

I tried to modify to work with Getx and get error: LateInitializationError: Field 'futureAlbum' has not been initialized. I thought onInit method should have initialized when controller instantiated in view_http.dart file. Thoughts?

CodePudding user response:

I dont think you need futureAlbum in HttpController. Just delete that, and change the code from httpController.futureAlbum to future: httpController.fetchAlbum. The other option is, change your ViewHttp to StatefulWidget, then put the futureAlbum there

late Future<Album> futureAlbum;
late HttpController httpController;

  @override
  void onInit() {
    super.onInit;
    httpController = HttpController();
    futureAlbum = httpController.fetchAlbum();
  }

Then in the future builder, use future: futureAlbum. Hope its help you.

CodePudding user response:

@Ananda is correct you do not need a separate variable of futureAlbum;

To add to that, when you initialize the object of a GetX class, you don't do this

  var httpController = HttpController();

You do this.

final httpController = Get.put(HttpController());

Or if you have already initialized somewhere else in the app you can find the same controller.

final httpController = Get.find<HttpController>();

When you call Get.put(...()) thats when the onInit functions is called. But in your case you don't even need that. Your GetX class can just be this.

class HttpController extends GetxController {
  Future<Album> fetchAlbum() async {
    final response = await http
        .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));

    if (response.statusCode == 200) {
      debugPrint('futureAlbum initialized');
      return Album.fromJson(jsonDecode(response.body));
    } else {
      throw Exception('Failed to load album');
    }
  }
}

And your ViewHttp can be like below. This will show your API data on screen.

class ViewHttp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final httpController = Get.put(HttpController());
    return Center(
        child: FutureBuilder<Album>(
      future: httpController.fetchAlbum(),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Text(snapshot.data!.title);
        } else if (snapshot.hasError) {
          return Text('${snapshot.error}');
        }

        // By default, show a loading spinner.
        return const CircularProgressIndicator();
      },
    ));
  }
}

Also as an FYI, while I do recommend GetX in general, it's not doing anything in your HttpController class. If that's all you plan on having that class do, then you can remove the GetX part, initialize it the way you were doing before and the functionality wouldn't change.

One of the main points of GetX (and most other state management solutions) is that you always access the same instance of the controller (unless you specifically need a new instance which usually you don't), and any data you store in that instances is easily accessible from anywhere in the app via Get.find<...>(). From there the onInit() becomes super handy and usually allows for all widgets to be stateless.

But as I said, if you're just doing a basic API all with that class, you don't need GetX there.

  • Related