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
  • 21,132
  • 4
  • 38
  • 37
MarsMan
  • 11
  • 5

2 Answers2

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
  • 838
  • 3
  • 7
-1

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

Adie RT
  • 71
  • 8
  • Programming is about science and *knowing* things. Not throwing characters around until one maybe sticks. In this case, it's even none of them. Please do not give this kind of "advice". Read the question, find the problem and write an answer. – nvoigt Jan 05 '22 at 07:46