flutterairtable

Flutter Airtable Data [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: Exception: 422


I have [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: Exception: 422 when i try to update some data of my Airtable Database. I can Get, Delete but it's impossible to Update my datas.

There is the code below.

airtable_data_candidate_http.dart


import 'package:http/http.dart' as http;
import 'package:tinjob/model/airtable_data_candidate.dart';

import 'package:tinjob/model/airtable_data_company.dart';
import 'package:tinjob/utils/config.dart';

class AirtableDataCandidateHttp {
  final Uri urlCandidate = Uri.https(
    "api.airtable.com",
    "/v0/${Config.airtableProjectBase}/candidat",
    {"maxRecords": "500", "view": "Grid view"},
  );

  Future<List<AirtableDataCandidate>> getCandidate() async {
    final res = await http.get(
      urlCandidate,
      headers: {"Authorization": "Bearer ${Config.airtableApikey}"},
    );

    if (res.statusCode == 200) {
      var convertDataToJson = jsonDecode(res.body);
      var data = convertDataToJson['records'];
      print(data);
      List<AirtableDataCandidate> values = [];
      data.forEach(
        (value) => {
          values.add(
            AirtableDataCandidate(
              id: value['id'],
              nom: value['fields']['nom'],
              prenom: value['fields']['prenom'],
              photo: value['fields']['photo'][0]['url'],
              statut: value['fields']['statut'],
              mail: value['fields']['mail'],
              localisation: value['fields']['localisation'],
              telephone: value['fields']['telephone'],
              match: value['fields']['match'],
              cv: value['fields']['cv'][0]['url'],
              lat: value['fields']['lat'].toDouble(),
              long: value['fields']['long'].toDouble(),
            ),
          )
        },
      );

      return values;
    } else {
      print('EEREUR');
      throw "ERROR !!!!!";
    }
  }

  // DELETE AIRTABLE CANDIDATE

  Future<String> deleteCandidate(String id) async {
    final response = await http.delete(
      Uri.https(
          "api.airtable.com", "/v0/${Config.airtableProjectBase}/candidat/$id"),
      headers: {"Authorization": "Bearer ${Config.airtableApikey}"},
      body: jsonEncode(<String, String>{}),
    );

    if (response.statusCode == 200) {
      // If the server did return a 200 OK response,
      // then parse the JSON.

      return id;
    } else {
      // If the server did not return a 200 OK response,
      // then throw an exception.
      throw Exception(response.statusCode);
    }
  }

  // Update AIRTABLE CANDIDATE

  Future<AirtableDataCandidate> updateCandidate(String id, String match) async {
    final response = await http.put(
      Uri.https(
          "api.airtable.com", "/v0/${Config.airtableProjectBase}/candidat/$id"),
      headers: {
        "Authorization": "Bearer ${Config.airtableApikey}",
      },
      body: jsonEncode(<String, String>{
        'match': match,
      }),
    );

    if (response.statusCode == 200) {
      // If the server did return a 200 OK response,
      // then parse the JSON.

      return AirtableDataCandidate.fromJson(jsonDecode(response.body));
    } else {
      // If the server did not return a 200 OK response,
      // then throw an exception.
      throw Exception(response.statusCode);
    }
  }
}

airtable_data_candidate.dart

  String id;
  String nom;
  String prenom;
  String photo;
  String statut;
  String telephone;
  String mail;
  String localisation;
  String cv;
  String match;
  double lat;
  double long;

  AirtableDataCandidate({
    required this.id,
    required this.nom,
    required this.prenom,
    required this.photo,
    required this.statut,
    required this.telephone,
    required this.mail,
    required this.localisation,
    required this.cv,
    required this.lat,
    required this.long,
    required this.match,
  });

  factory AirtableDataCandidate.fromJson(Map<String, dynamic> json) {
    return AirtableDataCandidate(
      id: json['id'],
      nom: json['fields']['nom'],
      prenom: json['fields']['prenom'],
      photo: json['fields']['photo'][0]['url'],
      statut: json['fields']['statut'],
      mail: json['fields']['mail'],
      localisation: json['fields']['localisation'],
      telephone: json['fields']['telephone'],
      match: json['fields']['match'],
      cv: json['fields']['cv'][0]['url'],
      lat: json['fields']['lat'].toDouble(),
      long: json['fields']['long'].toDouble(),
    );
  }
}

swipe.dart --> Where i make the action of update (if swipe left then update...)


import 'package:flutter/material.dart';
import 'package:flutter_tindercard/flutter_tindercard.dart';
import 'package:tinjob/model/airtable_data_candidate.dart';
import 'package:tinjob/service/airtable_data_candidate_http.dart';
import 'package:tinjob/widgets/widget_drawer.dart';

class SwipeScreen extends StatefulWidget {
  const SwipeScreen({Key? key}) : super(key: key);

  @override
  _SwipeScreenState createState() => _SwipeScreenState();
}

class _SwipeScreenState extends State<SwipeScreen>
    with TickerProviderStateMixin {
  final AirtableDataCandidateHttp airtableData = AirtableDataCandidateHttp();

  List<String> welcomeImages = [];
  List<AirtableDataCandidate> candidatesDatas = [];

  setPictures() async {
    var candidates = await airtableData.getCandidate();
    for (var candidate in candidates) {
      setState(() {
        welcomeImages.add(candidate.photo);
        candidatesDatas.add(candidate);
      });
    }
  }

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

  @override
  Widget build(BuildContext context) {
    CardController controller; //Use this to trigger swap.

    return new Scaffold(
      appBar: AppBar(title: const Text('Les derniers candidats')),
      drawer: drawer(context),
      body: new Center(
        child: SizedBox(
          height: MediaQuery.of(context).size.height * 0.6,
          child: new TinderSwapCard(
            swipeUp: false,
            swipeDown: false,
            orientation: AmassOrientation.BOTTOM,
            totalNum: welcomeImages.length,
            stackNum: 3,
            swipeEdge: 4.0,
            maxWidth: MediaQuery.of(context).size.width * 0.9,
            maxHeight: MediaQuery.of(context).size.width * 0.9,
            minWidth: MediaQuery.of(context).size.width * 0.8,
            minHeight: MediaQuery.of(context).size.width * 0.8,
            cardBuilder: (context, index) => Card(
              child: Image.network('${welcomeImages[index]}'),
            ),
            cardController: controller = CardController(),
            swipeUpdateCallback: (DragUpdateDetails details, Alignment align) {
              /// Get swiping card's alignment
              if (align.x < 0) {
                //Card is LEFT swiping

              } else if (align.x > 0) {
                //Card is RIGHT swiping
              }
            },
            swipeCompleteCallback:
                (CardSwipeOrientation orientation, int index) {
              /// Get orientation & index of swiped card!
              print('${candidatesDatas[index].match}');
              print(orientation);
              if (orientation == CardSwipeOrientation.LEFT) {
                print('LEFT');
                candidatesDatas[index].match = 'Refusé';
                print('${candidatesDatas[index].match}');
                airtableData.updateCandidate(
                    candidatesDatas[index].id, candidatesDatas[index].match);
              } else if (orientation == CardSwipeOrientation.RIGHT) {
                //Card is RIGHT swiping
                print('RIGHTEZBI');
                candidatesDatas[index].match = 'Admis';
                print('${candidatesDatas[index].match}');
              }
            },
          ),
        ),
      ),
    );
  }
}

Hope that anybody can find me the solution, i'm totaly out :(

Thanks !


Solution

  • update

    1. don't put your id in the Url
    2. put Content-Type in headers
    3. change your body to pass a records, with id + fields to update

    Not tested with your exact structure, so tell me...

    Future<AirtableDataCandidate> updateCandidate(String id, String match) async {
        final response = await http.put(
            Uri.https(
                "api.airtable.com", "/v0/${Config.airtableProjectBase}/candidat/", /* 1 */
            ),
            headers: {
                "Authorization": "Bearer ${Config.airtableApikey}",
                "Content-Type": "application/json", /* 2 */
            },
            body: jsonEncode( /* 3 */
                {
                    "records": [
                    {
                        "id": $id,
                        "fields": {
                            'match': match,
                        }
                    },
                    ]
                },
            ),
        )
       /// etc.
    

    insert (for reference)

    1. be in POST
    2. don't use ID...
    3. list all your fields...
    final response = await http.post /* 1 */ (
        Uri.https(
            "api.airtable.com", "/v0/${Config.airtableProjectBase}/candidat/", 
        ),
        headers: {
            "Authorization": "Bearer ${Config.airtableApikey}",
            "Content-Type": "application/json", 
        },
        body: jsonEncode( 
            {
                "records": [
                {
                    /* 2 */
                    "fields": {
                        "x": "", /* 3 */
                        "y": "", /* 3 */
                        "z": "", /* 3 */
                    }
                },
                ]
            },
        ),
    )