0

appreciate the help! I've looked through some of the other responses on here and I can't find an answer.

I have a Provider, in which I have an async function defined. It reaches out to an external API, gets data, and then is meant to update the attributes in the Provider with the data received.

The Widget that uses the provider is meant to build a ListView with that data. projects is null until the response is received. That's why I need the async await functionality to work here. The error I'm getting says that "length can't be called on null", which means projects is still null at the time is reaches that line. That is because the async functionality isn't working.

Here is the Provider, in which my async function is defined:

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

import 'dart:convert';

import '../../constants/urls.dart';

import 'project.dart';

class Projects with ChangeNotifier{
  List<Project> _projects;
  List<Project> _myProjects;
  final String authToken;
  final List<Project> previousProjects;
  final bool _initialLoad = true;

  Projects(this.authToken, this.previousProjects);

  List<Project> get projects {
    return _projects;
  }
  List<Project> get myProjects {
    return _myProjects;
  }
  bool get initialLoad {
    return _initialLoad;
  }

  Future<void> fetchProjects() async {

    print('inside future, a');
    try {
      var response = await http.get(
        Uri.parse(Constants.fetchProjectsURL),
        headers: {"Authorization": "Bearer " + authToken},
      );
      print('inside future, b');
      if (response.statusCode == 200) {
        final extractedData = json.decode(response.body) as List;
        final List<Project> tempLoadedProjects = [];
        extractedData.forEach((project) {
          tempLoadedProjects.add(
            Project(
              // insert project params
            ),
          );
        });
        _projects = tempLoadedProjects;
        print(_projects);
        print(projects);
        notifyListeners();
      } else {
        print('something happened');
      }
    } catch (error) {
      throw error;
    }

  }

}

Then, I used this provider in the following Widget:

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

import '../../../providers/projects/projects_provider.dart';

class ProjectsColumn extends StatelessWidget {

  Future<void> fetchProjects(ctx) async {
    await Provider.of<Projects>(ctx).fetchProjects();
  }

  Widget build(BuildContext context) {
    print('Before fetch');
    fetchProjects(context);
    print('After fetch');
    final projects = Provider.of<Projects>(context, listen: false).projects;

    return ListView.builder(
        itemCount: projects.length,
        itemBuilder: (BuildContext ctx, int index) {
          return Card(
            child: Text(
              'Project Name:${projects[index]}',
            ),
          );
        });
  }
}

Thoughts?

user18349414
  • 41
  • 1
  • 3
  • Please try this https://pastebin.com/gX5gtnEy. I'll explain what I've done if this works for you. – Swanav Mar 02 '22 at 03:43
  • 1
    Your `build()` method calls `fetchProjects(context)`, but that's an asynchronous method,, and `build` doesn't wait for it to complete. If you need a `build` method to wait for a `Future`, you should use a `FutureBuilder`, as explained by [What is a Future and how do I use it?](https://stackoverflow.com/questions/63017280/) – jamesdlin Mar 02 '22 at 03:57
  • You should initialize your **_projects** List like `List _projects = [];` to get rid of your **"length can't be called on null"** error. And you should also remove `listen: false` from `final projects = Provider.of(context, listen: false).projects;` line to recieve updates for your **_projects** list. – Ante Bule Mar 02 '22 at 07:26

1 Answers1

0

You need to put await before the method to a wait, but you can't do this in build() method, So you can use future builder like the answer of @jamesdlin or you can call fetchProjects method in intState first like this way:

class ProjectsColumn extends StatefulWidget {
  @override
  State<ProjectsColumn> createState() => _ProjectsColumnState();
}

class _ProjectsColumnState extends State<ProjectsColumn> {
  bool _isLoading = true;

  Future<void> _fetchProjects() async {
    await Provider.of<Projects>(context, listen: false).fetchProjects();
    _isLoading = false;
    if (mounted) setState(() {});
  }

  @override
  void initState() {
    super.initState();
    _fetchProjects();
  }

  @override
  Widget build(BuildContext context) {
    return _isLoading
        ? const Center(child: CircularProgressIndicator())
        : Consumer<Projects>(
            builder: (context, builder, child) => builder.projects.isEmpty
                ? const Center(child: Text('No Projects Found'))
                : ListView.builder(
                    shrinkWrap: true,
                    itemCount: builder.projects.length,
                    itemBuilder: (BuildContext ctx, int index) {
                      return Card(
                        child: Text(
                          'Project Name:${builder.projects[index]}',
                        ),
                      );
                    },
                  ),
          );
  }
}

EDIT: a) From the docs HERE BuildContext objects are passed to WidgetBuilder functions (such as StatelessWidget.build), and are available from the State.context member., and in the previous example I used StatefulWidget widget that extends state class, then you can use context outside build but inside the class extends state, not like StatelessWidget.
b) mounted condition, it represents whether a state is currently in the widget tree, i used it to prevent the famous error: setState() called after dispose()
see docs HERE, also this useful answer HERE

mohmdezeldeen
  • 396
  • 1
  • 3
  • 11
  • Thank you! This all makes great sense now. You rock! – user18349414 Mar 02 '22 at 21:45
  • I have a follow up question @mohmdezeldeen. These are more general and might reveal that I am a novice, but a) how are we able to use context here outside of the build method? and b) What does mounted mean? I've never seen that used before. Thank you! – user18349414 Mar 02 '22 at 22:02
  • I have modified my answer for more clarification @user18349414 – mohmdezeldeen Mar 06 '22 at 08:09