0

I am trying to get all the cars from my backend when I land on the car_map screen but I get the following exception.

I use Futures to get the list of the cars.

======== Exception caught by widgets library =======================================================
The following NoSuchMethodError was thrown building FutureBuilder<List<Object>>(dirty, dependencies: [MediaQuery], state: _FutureBuilderState<List<Object>>#33dda):
The method 'forEach' was called on null.
Receiver: null
Tried calling: forEach(Closure: (CarModel) => Null)

When the exception was thrown, this was the stack: 
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:63:5)
#1      _CarMapState._carsMapView (package:Caroo/screens/car_map/car_map.dart:121:13)
#2      _CarMapState._carsMapWidget.<anonymous closure> (package:Caroo/screens/car_map/car_map.dart:242:22)
#3      _FutureBuilderState.build (package:flutter/src/widgets/async.dart:782:55)
#4      StatefulElement.build (package:flutter/src/widgets/framework.dart:4782:27)
#5      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4665:15)
#6      StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4840:11)
#7      Element.rebuild (package:flutter/src/widgets/framework.dart:4355:5)
import 'package:flutter/material.dart';
import 'package:latlong2/latlong.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:geolocator/geolocator.dart';

import 'package:Caroo/bloc.navigation_bloc/navigation_bloc.dart';
import 'package:Caroo/style.dart';
import 'package:Caroo/api.dart';
import 'package:Caroo/models/car.dart';
import 'package:Caroo/screens/car_map/car_map_item.dart';

class CarMap extends StatefulWidget with NavigationStates {
  CarMap({this.onPush, this.userID});
  final String userID;
  final Function onPush;
  @override
  _CarMapState createState() => new _CarMapState();
}

class _CarMapState extends State<CarMap> {
  final Api api = Api();
  Future<List<CarModel>> _cars;
  Future<List<bool>> _userState;
  Future<Position> _currPosition;
  int _selectedCar = -1;

  MapController controller = MapController();

  Future<List<CarModel>> _getCars() async {
    return await api.getCarList().catchError((e) {
      if (e.toString() == 'NO_AUTH') {
        ScaffoldMessenger.of(context).showSnackBar(SnackBar(
            content: Text('Error in cars list'),
            duration: Duration(seconds: 5),
            backgroundColor: Colors.red));
        throw Exception('error');
      }
    });
  }

  Future<List<bool>> _getUserState() async {
    return await api.userState().catchError((e) {
      if (e.toString() == 'NO_AUTH') {
        ScaffoldMessenger.of(context).showSnackBar(SnackBar(
            content: Text('Error in user state'),
            duration: Duration(seconds: 5),
            backgroundColor: Colors.red));
        throw Exception('Error in user state');
      }
    });
  }

  Future<Position> _getCurrPosition() async {
    Future<Position> result =
        Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.best);
    return result;
  }

  // void filterCars() async {
  //   var filtering = await widget.onPush(context, widget, '/map_filter', null);
  //   setState(() {});
  // }

  @override
  void initState() {
    super.initState();
    _userState = _getUserState();
    _cars = _getCars();
    _currPosition = _getCurrPosition();
  }

  // @override
  // void dispose() {
  //   super.dispose();
  //
  // }

  void _onCarTapped(CarModel car, bool isAuthorized) {
    if (car.isAvailable) {
      showModalBottomSheet<void>(
          context: context,
          backgroundColor: Colors.transparent,
          builder: (BuildContext context) {
            return CarMapItem(
              car: car,
              onPush: widget.onPush,
              isAuthorized: isAuthorized,
            );
          });
    }
    debugPrint('Marker tapped ' + _selectedCar.toString());
  }

  Widget _carsMapView(BuildContext context, AsyncSnapshot snapshot) {
    Size size = MediaQuery.of(context).size;
    // final List<CarModel> carList = snapshot.data;
    // final bool isAuthorized = true;
    // final bool isRejected = true;
    final List<CarModel> carList = snapshot.data[0];
    debugPrint(snapshot.data[0]);
    final bool isAuthorized = snapshot.data[1][0];
    final bool isRejected = snapshot.data[1][1];
    final Position _currPosition = snapshot.data[2];

    List<Marker> markers = [
      new Marker(
        width: 45.0,
        height: 45.0,
        point: LatLng(_currPosition.latitude, _currPosition.longitude),
        builder: (context) => new Container(
          child: IconButton(
            icon: Icon(Icons.location_on),
            color: Colors.red,
            iconSize: 45.0,
            onPressed: () => {},
          ),
        ),
      ),
    ];
    carList.forEach((car) {
      markers.add(
        new Marker(
          width: 45.0,
          height: 45.0,
          point: new LatLng(car.latitude, car.longitude),
          builder: (context) => new Container(
            child: IconButton(
              icon: Icon(Icons.location_on),
              color: car.isAvailable ? PrimaryColor : Colors.grey[500],
              iconSize: 45.0,
              onPressed: () => _onCarTapped(car, isAuthorized),
            ),
          ),
        ),
      );
    });

    return new Scaffold(
      floatingActionButton: Padding(
        padding: EdgeInsets.only(bottom: 60),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: <Widget>[
            //!! FILTER CARS !!
            // Container(
            //   width: 35,
            //   child: FloatingActionButton(
            //     heroTag: "Filter",
            //     tooltip: 'Cars filter',
            //     onPressed: () => filterCars(),
            //     child: Icon(Icons.filter_alt, size: 18, color: PrimaryColor,),
            //     backgroundColor: Colors.white,
            //   ),
            // ),
            // SizedBox(height: size.height*0.01),
            Container(
              width: 50,
              child: FloatingActionButton(
                heroTag: "Recenter",
                tooltip: 'Recenter',
                onPressed: () {
                  setState(() {
                    _getCurrPosition();
                  });
                  // TODO: Change move controller.
                  controller.onReady;
                  controller.move(
                      new LatLng(
                          _currPosition.latitude, _currPosition.longitude),
                      controller.zoom);
                },
                child: Icon(
                  Icons.gps_fixed,
                  size: 25,
                  color: PrimaryColor,
                ),
                backgroundColor: Colors.white,
              ),
            ),
          ],
        ),
      ),
      body: Container(
        // height: size.height,
        child: Stack(
          children: [
            FlutterMap(
              // mapController: controller,
              options: MapOptions(
                center: LatLng(_currPosition.latitude, _currPosition.longitude),
                minZoom: 15.0,
              ),
              layers: [
                new TileLayerOptions(
                  // urlTemplate: "https://api.mapbox.com/styles/v1/dbalaskas/ckf8bizf717go19mkvp48ifvf/tiles/256/{z}/{x}/{y}@2x?access_token=pk.eyJ1IjoiZGJhbGFza2FzIiwiYSI6ImNrZjg5NG41OTBhMDYycm1hM3RzYWVpbXoifQ.jjsmEKanqLYld1dXfkLPRQ",
                  urlTemplate:
                      "https://api.mapbox.com/styles/v1/dbalaskas/ckg7znggm231g19qowxpz2xq5/tiles/256/{z}/{x}/{y}@2x?access_token=pk.eyJ1IjoiZGJhbGFza2FzIiwiYSI6ImNrZjg5NG41OTBhMDYycm1hM3RzYWVpbXoifQ.jjsmEKanqLYld1dXfkLPRQ",
                  additionalOptions: {
                    // 'accessToken': 'pk.eyJ1IjoiZGJhbGFza2FzIiwiYSI6ImNrZjg5NG41OTBhMDYycm1hM3RzYWVpbXoifQ.jjsmEKanqLYld1dXfkLPRQ',
                    // 'id': 'mapbox.mapbox-streets-v8'
                    'accessToken':
                        'pk.eyJ1IjoiZGJhbGFza2FzIiwiYSI6ImNrZjg5NG41OTBhMDYycm1hM3RzYWVpbXoifQ.jjsmEKanqLYld1dXfkLPRQ',
                    'id': 'mapbox://styles/dbalaskas/ckg7znggm231g19qowxpz2xq5'
                  },
                ),
                new MarkerLayerOptions(
                  markers: markers,
                )
              ],
            ),
            if (!isAuthorized && !isRejected)
              Positioned(
                left: size.width * 0.025,
                top: size.height * 0.05,
                child: _notAuthorized(context),
              ),
            if (isRejected)
              Positioned(
                left: size.width * 0.025,
                top: size.height * 0.05,
                child: _notAcceptedLicense(context),
              )
          ],
        ),
      ),
    );
  }

  Widget _carsMapWidget() {
    return FutureBuilder(
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        switch (snapshot.connectionState) {
          case ConnectionState.waiting:
            return Center(child: CircularProgressIndicator());
            break;
          case ConnectionState.done:
            if (snapshot.hasError) {
              debugPrint("ERROR_CAR_MAP >> ${snapshot.hasError}");
              return widget.onPush(context, widget, '/logout', null);
            } else if (snapshot.hasData) {
              return _carsMapView(context, snapshot);
            } else {
              return Center(
                child: Text("No data"),
              );
            }
            break;
          default:
            if (snapshot.hasError) {
              debugPrint("ERROR_CAR_MAP >> ${snapshot.hasError}");
              return widget.onPush(context, widget, '/logout', null);
            } else {
              return _carsMapView(context, snapshot);
            }
        }
      },
      future: Future.wait([_cars, _userState, _currPosition]),
      // future: _cars,
    );
  }

  @override
  Widget build(BuildContext context) {
    // _userState = _getUserState();
    // _cars = _getCars();
    // _currPosition = _getCurrPosition();

    return Scaffold(
      body: WillPopScope(
        onWillPop: () async {
          return Future.value(true);
        },
        child: _carsMapWidget(),
      ),
    );
  }
}

Widget _notAuthorized(BuildContext context) {
  Size size = MediaQuery.of(context).size;
  return Container(
    // height: size.height*0.1,
    width: size.width * 0.95,
    margin: EdgeInsets.symmetric(vertical: 10),
    padding: EdgeInsets.all(10),
    decoration: BoxDecoration(
      color: NotAuthorizedBoxColor,
      border: Border.all(color: Colors.black38, width: 1.3),
      borderRadius: BorderRadius.all(Radius.circular(20)),
    ),
    child: Wrap(
      alignment: WrapAlignment.center,
      children: [
        Text(
          'Please wait for our team to authenticate your license.',
          style: TextStyle(fontWeight: FontWeight.w600, fontSize: 18),
          textAlign: TextAlign.center,
        ),
        Text(
          'Thank you!',
          style: TextStyle(fontSize: 18),
        )
      ],
    ),
  );
}

Widget _notAcceptedLicense(BuildContext context) {
  Size size = MediaQuery.of(context).size;

  return Container(
    width: size.width * 0.95,
    margin: EdgeInsets.symmetric(vertical: 10),
    padding: EdgeInsets.all(10),
    decoration: BoxDecoration(
      color: NotAcceptedBoxColor,
      border: Border.all(color: Colors.black38, width: 1.3),
      borderRadius: BorderRadius.all(Radius.circular(20)),
    ),
    child: Column(
      children: [
        Text(
          'Please insert again pictures of your license to continue.',
          style: TextStyle(fontWeight: FontWeight.w600, fontSize: 18),
          textAlign: TextAlign.center,
        ),
        Text(
          'Thank you!',
          style: TextStyle(fontSize: 18),
        )
      ],
    ),
  );
}

My API call:

Future<List<CarModel>> getCarList() async {
    String token = await storage.getToken();
    debugPrint('(CARs) TOKEN: ' + token);
    /*final response =
        await http.get(Uri.encodeFull(_api_url + '/api/v1/cars/'), headers: {
      'Accept': 'application/json; charset=utf-8',
      HttpHeaders.authorizationHeader: 'Token $token'
    });*/
    final response =
        await http.get(Uri.parse(_api_url + '/api/v1/cars/'), headers: {
      'Accept': 'application/json; charset=utf-8',
      HttpHeaders.authorizationHeader: 'Token $token'
    });
    debugPrint('getCarList response: ${response.body.toString()}');
    if (response.statusCode == 200) {
      return json
          .decode(utf8.decode(response.bodyBytes))['result']
          .map<CarModel>((item) {
        try {
          return CarModel.fromMappedJson(item);
        } catch (e) {
          return CarModel.fromMappedJson(item);
        }
      }).toList();
    } else if (response.statusCode == 401) {
      throw "get_car_list NO_AUTH";
    } else {
      throw "get_car_list ERROR";
    }
  }

getCarList response:

{
  "result": [
    {
      "id": 1,
      "car_brand": "SCONDA",
      "car_model": "OCTAVIA",
      "car_picture": "photos/cars/2021/11/14/%{self.brand}/%{self.model}/261-2616422_skoda-octavia-png-skoda-s_JfP1yEL.png",
      "car_seatsNum": 5,
      "city": "Athens",
      "owner": "ARAMS.IKE",
      "manufacturingDate": "2012",
      "plate": "ZAZ 2120",
      "price": "6.000",
      "longitude": "23.769970",
      "latitude": "37.977850",
      "is_available": true,
      "hasBluetooth": true,
      "hasChildSeat": false,
      "isPetFriendly": false
    },
    {
      "id": 2,
      "car_brand": "HUYNDAI",
      "car_model": "i20",
      "car_picture": "photos/cars/2021/11/14/%{self.brand}/%{self.model}/huyndai_i20.png",
      "car_seatsNum": 5,
      "city": "Athens",
      "owner": "ARAMS.IKE",
      "manufacturingDate": "2018",
      "plate": "ΡÎÎ1234",
      "price": "6.000",
      "longitude": "23.758440",
      "latitude": "37.977460",
      "is_available": true,
      "hasBluetooth": true,
      "hasChildSeat": true,
      "isPetFriendly": false
    }
  ]
}
julemand101
  • 28,470
  • 5
  • 52
  • 48
MarsMan
  • 21
  • 8

2 Answers2

0

Try to make your forEach Nullable by adding ! or ? . Try this forEach ? or forEach ! instead of forEach. Check out this Null Safety Migration Guide

Adeel Nazim
  • 640
  • 6
  • 17
0

To make things easier and your code more organized, I would recommend not assigning 3 different futures to your FutureBuilder but rather using one future function, your _cars Future, and call the other _getUserState() and _getCurrPosition() futures inside it and assign the return values of those other functions to local variables in the widget.

  1. For your _getCars() & _getUserState() futures in your widget, you have to use either async/await & try/catch OR then/catchError using both is causing not to return the future of your api call correctly. So I would rewrite the function like so:
List<bool> _userState;
Position _currPosition;

Future<void> _getUserState() async {
  try {
    _userState = await api.userState();
  } catch (e) {
    if (e.toString() == 'NO_AUTH') {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
          content: Text('Error in user state'),
          duration: Duration(seconds: 5),
          backgroundColor: Colors.red));
      throw Exception('Error in user state');
    }
  }
}

Future<void> _getCurrPosition() async {
  _currPosition = Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.best);
}

Future<List<CarModel>> _getCars() async {
  try {
    await _getUserState();
    await _getCurrPosition();
    return await api.getCarList();
  } catch (e) {
    if (e.toString() == 'NO_AUTH') {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
          content: Text('Error in cars list'),
          duration: Duration(seconds: 5),
          backgroundColor: Colors.red));
      throw Exception('error');
    }
  }
}
  1. In your FutureBuilder widget, your future value should be: future: _cars, as the return value of this function (which is List<CarModel> will be assigned to your snapshot.data so I also recommend changing your builder method to assign the return type of your AsyncSnapshot. Your FutureBuilder widget should look something like this:
FutureBuilder(
   future: _cars,
   builder: (BuildContext context, AsyncSnapshot<List<CarModel>> snapshot) {
      //...
   }
)

  1. Instead of return _carsMapView(context, snapshot); in your FutureBuilder, make it:
return _carsMapView(context, snapshot.data);

  1. Change your _carsMapView() function accordingly like so:
Widget _carsMapView(BuildContext context, List<CarModel> carList) {
  Size size = MediaQuery.of(context).size;
  final bool isAuthorized = _userState[0];
  final bool isRejected = _userState[1];

  List<Marker> markers = [
    new Marker(
      width: 45.0,
      height: 45.0,
      point: LatLng(_currPosition.latitude, _currPosition.longitude),
      builder: (context) =>
      new Container(
        child: IconButton(
          icon: Icon(Icons.location_on),
          color: Colors.red,
          iconSize: 45.0,
          onPressed: () => {},
        ),
      ),
    ),
  ];
  carList.forEach((car) {
    markers.add(
      new Marker(
        width: 45.0,
        height: 45.0,
        point: new LatLng(car.latitude, car.longitude),
        builder: (context) =>
        new Container(
          child: IconButton(
            icon: Icon(Icons.location_on),
            color: car.isAvailable ? PrimaryColor : Colors.grey[500],
            iconSize: 45.0,
            onPressed: () => _onCarTapped(car, isAuthorized),
          ),
        ),
      ),
    );
  });
  
  //...
}

Extra recommendations:

  1. Optimize your getCarsList() api call function like so:
Future<List<CarModel>> getCarList() async {
  String token = await storage.getToken();
  debugPrint('(CARs) TOKEN: ' + token);
  final response =
  await http.get(Uri.parse(_api_url + '/api/v1/cars/'), headers: {
    'Accept': 'application/json; charset=utf-8',
    HttpHeaders.authorizationHeader: 'Token $token'
  });
  debugPrint('getCarList response: ${response.body}');
  if (response.statusCode == 200) {
    final responseData = json.decode(response.body);
    List<CarModel> carsList = List<CarModel>.from(responseData['results'].map((x) => CarModel.fromMappedJson(x)));
    return carsList;
  } else if (response.statusCode == 401) {
    throw "get_car_list NO_AUTH";
  } else {
    throw "get_car_list ERROR";
  }
}

The json.decode() function here is enough to convert the string of the response.body into a map that you can then extract your results list from and convert it to a list of your model using this quick line of code:

List<CarModel> carsList = List<CarModel>.from(responseData['results'].map((x) => CarModel.fromMappedJson(x)));
  1. Overall I'd recommend you separate your code into smaller widgets rather than a very long build method. Check out this video from Flutter team regarding this issue.
Roaa
  • 1,212
  • 7
  • 11