0

I am working on a mobile app related to vehicles. I have to create a form that should have several fields to be filled about a vehicle's info (like regNum, brand, model,type...). In order to fetch the data for the dropdown button field I have to make http request(for type,brand,model).

I want whenever I change the vehicle brand in its corresponding dropdown, the vehicle model dropdown field to be updated only with models corresponding to the selected brand.

Here is my code: #VehicleForm

class VehicleForm extends StatefulWidget {
  final Future<VehicleTypes> types;
  final Future<VehicleBrands> brands;
  final Future<VehicleModels> models;
  
  VehicleForm(this.types, this.brands, this.models);
    
  @override
  VehicleFormState createState() => VehicleFormState(types,brands,models);
}

class VehicleFormState extends State<VehicleForm>{
  final Future<VehicleTypes> types;
  final Future<VehicleBrands> brands;
  final Future<VehicleModels> models;
  String brandName;
    
  VehicleFormState(this.types, this.brands, this.models);
    
  void handleBrandChanged(String brand){
    setState(() {
      print(brand);
      brandName=brand;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Vehicle')
      ),
      body: Container(
        child: Column(
          children: [
            Container(
              margin: EdgeInsets.only(
                top:20,
              ),
              alignment: Alignment.center,
              child:Text('General Info',
                style: TextStyle(
                  fontSize: 22,
                  color:Colors.blue,
                ),
              ),
            ),
            Container(
              child: Column(
              children: [
                Container(
                  child: TextFormField(
                    decoration: const InputDecoration(
                        hintText: 'Registration number'
                    ),
                  ),
                  margin: EdgeInsets.all(10),
                ),
                Container(
                  child: TextFormField(
                    decoration: const InputDecoration(
                      hintText: 'Vehicle km'
                    ),
                  ),
                  margin: EdgeInsets.all(10),
                ),
                Container(
                  width:200,
                  child: VehicleTypeMenu(types),
                ),
                Container(
                  width:200,
                  child: VehicleBrandMenu(brands,brandName,handleBrandChanged),
                ),
                Container(
                  width:250,
                  child: brandName==null ? TextFormField(
                    decoration: const InputDecoration(
                      hintText: 'Vehicle Model'
                    ),
                  ): VehicleModelMenu(models,brandName),
                ),
                VehicleYearDropdown(),
                VehicleMonthDropdown(),
              ],
            ),
          )
        ],
     )
   )
 );
}
    
    
//VehicleBrand
class VehicleBrandMenu extends StatelessWidget{
  final Future<VehicleBrands> brands;
  final String brandName;
  final ValueChanged<String> onChanged;
   
  VehicleBrandMenu(this.brands,this.brandName,this.onChanged);
    
  void handleBrandChanged(String brandName){
    onChanged(brandName);
  }
    
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<VehicleBrands>(
      future: brands,
      builder: (context,snapshot){
        if(snapshot.hasData){
          List<String> vehicleBrands = List<String>();
          for(int i=snapshot.data.brands.length-1;i>=0;i--){
            vehicleBrands.add(snapshot.data.brands[i]['name'].toString());
          }
          return DropdownButton<String>(
            hint:  Text("Select Vehicle Brand"),
            value:brandName,
            onChanged: handleBrandChanged,
            items: vehicleBrands.map((String vehicleBrand){
              return DropdownMenuItem(
                value:vehicleBrand,
                child: Row(
                  children: [
                    Text('$vehicleBrand')
                  ],
                ),
              );
            }).toList(),
          );
        } else if(snapshot.hasError){
          return Text('${snapshot.error}');
        } else{
          return TextFormField(
            decoration: const InputDecoration(
              hintText: 'Vehicle Model'
            ),
          );
        }
      }
    );
  }
}
//VehicleModel(the problem occurs here)!
    
class VehicleModelMenu extends StatefulWidget{
  final Future<VehicleModels> models;
  final String brandName;
    
  VehicleModelMenu(this.models,this.brandName);
    
  @override
  VehicleModelMenuState createState() => VehicleModelMenuState(models,brandName);
}
    
class VehicleModelMenuState extends State<VehicleModelMenu>{
  final Future<VehicleModels> models;
  final String brandName;
  var firstItem;
    
  VehicleModelMenuState(this.models,this.brandName);
    
  @override
  Widget build(BuildContext context) {
    return FutureBuilder<VehicleModels>(
      future: models,
      builder: (context,snapshot){
        if(snapshot.hasData){
          print(brandName);
          List<String> vehicleModels = List<String>();
          for(int i=snapshot.data.models.length-1;i>=0;i--){ //The problem occurs in this loop
            if(snapshot.data.models[i]['vehicleBrand']['name']==brandName){ //I check for equal brand
              vehicleModels.add(snapshot.data.models[i]['name']); //I add only the needed models
            }
          }
          return DropdownButton<String>(
            hint: Text("Select Vehicle Model"),
            value: firstItem,
            onChanged: (String model) {
              setState(() {
                firstItem = model;
              });
            },
            items: vehicleModels.map((String vehicleModel) {
              return DropdownMenuItem(
                value: vehicleModel,
                child: Row(
                  children: [
                    Text('$vehicleModel')
                  ],
                ),
              );
            }).toList(),
          );
        } else if(snapshot.hasError){
          return Text('${snapshot.error}');
        } else {
          return CircularProgressIndicator();
        }
      }
    );
  }
}

Here is the data I want to fetch: I compare the ['vehicleBrand']['name']->brand property and add ['name']->model

enter image description here

Here is the actual error:

======== Exception caught by widgets library =======================================================
The following NoSuchMethodError was thrown building FutureBuilder<VehicleModels>(dirty, state: _FutureBuilderState<VehicleModels>#5813d):
The method '[]' was called on null.
Receiver: null
Tried calling: []("name")

The relevant error-causing widget was: 
  FutureBuilder<VehicleModels> file:///D:/Android%20Apps/login_form/lib/vehicleFormElements/vehicleModel.dart:23:12
When the exception was thrown, this was the stack: 
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
#1      VehicleModelMenuState.build.<anonymous closure> (package:test_flutter_app/vehicleFormElements/vehicleModel.dart:30:59)
#2      _FutureBuilderState.build (package:flutter/src/widgets/async.dart:751:55)
#3      StatefulElement.build (package:flutter/src/widgets/framework.dart:4744:28)
#4      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4627:15)

Here is the deserialisation to object

import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;

class VehicleModels {
  final List<dynamic> models;

  VehicleModels({this.models});

  factory VehicleModels.fromJson(Map<String,dynamic> json){
    return VehicleModels(
      models: json['data']['results'],
    );
  }
}

Future<VehicleModels> getVehicleModels(String cookie)async{
  final http.Response response = await http.get(
    'https://gara6.bg/auto-api/vehicleModels?pageSize=2147483647',
    headers: <String, String>{
      'Content-Type': 'application/json',
      'cookie':cookie,
    },
  );

  if(response.statusCode==200){
    return VehicleModels.fromJson(jsonDecode(utf8.decode(response.bodyBytes)));
  }
  else{
    throw Exception('Failed to retrieve vehicle models');
  }
}

1 Answers1

0

If any entry in your JSON is missing vehicleBrand you will get that null error.

Since you're accessing nested JSON data (i.e. Map class) directly, you have to check each nested level actually has data or else you can get a null error trying to access a value when the object is null.

So this:

if (snapshot.data.models[i]['vehicleBrand']['name']==brandName) {
  // do something
}

should be something like this:

if (snapshot.data.models[i] != null && snapshot.data.models[i]['vehicleBrand'] != null && snapshot.data.models[i]['vehicleBrand']['name'] == brandName) {
  // do something
}

In general, directly accessing JSON data like this is unsafe, repetitive and verbose. It would probably be better to convert your JSON data into objects (i.e. deserialize) where you can get the benefits of Type-safety (properties are the type you're expecting) & can create methods/getters that produce safe/sane values so you don't get null errors when data isn't perfect.

Check out the Flutter article on serialization for more info.

Baker
  • 24,730
  • 11
  • 100
  • 106
  • thanks a lot for the idea,the code actually worked. It seems that there is null data somewhere. – Aleksander Aleksiev Feb 09 '21 at 14:52
  • Also I looked at the article you show me and I think I have already done deserialization to a class. Would you take a look at the code I will add to assert me if I do it the right way. Thank you in advance. – Aleksander Aleksiev Feb 09 '21 at 14:55
  • Hey @AleksanderAleksiev. That's good you've created deserialiation methods in your `VehicleModels` class. I noticed that a null error could still be thrown though. For example: `json['data']['results']. If json['data'] is null, that line will throw. You could let https://pub.dev/packages/json_serializable do the work for you, which is safer & likely easier. (Made by Google.) Info on how to set up your classes for that package: https://stackoverflow.com/a/63473168/2301224 – Baker Feb 10 '21 at 01:09