Dart类型错误:'Null'不是'int'的子类型问题求助
问题排查:
type 'Null' is not a subtype of type 'int'错误解决 错误详情
页面初始化时触发错误:type 'Null' is not a subtype of type 'int',关联错误组件为MaterialApp,栈追踪如下:
MaterialApp MaterialApp:file:///D:/Users/lebourgeois/android_studio_projects/tp1/lib/coursSQLite/main.dart:10:12 When the exception was thrown, this was the stack: #0 new Personne.fromMap (package:tp1/coursSQLite/Personne.dart:24:5) #1 _PersonnePageState.initState (package:tp1/coursSQLite/PersonnePage.dart:33:36)
错误核心原因:Personne类的id字段定义为非空int类型,但初始化时传入的mapPersonne不包含id键(新创建的记录未插入数据库,id由SQLite自增生成),导致map[colonneId]返回null,无法赋值给非空int类型变量。
相关代码
Personne.dart
import 'dart:io'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sqflite/sqflite.dart'; const String tablePersonne = 'personne'; const String colonneId = 'id'; const String colonneNom = 'nom'; const String colonnePrenom = 'prenom'; const String colonneAge = 'age'; const String databaseName = 'PersonneDB.db'; const int databaseVersion = 1; class Personne { late int id; late String nom; late String prenom; late String age; Personne(); Personne.fromMap(Map<String, dynamic> map) { id = map[colonneId]; nom = map[colonneNom]; prenom = map[colonnePrenom]; age = map[colonneAge]; } Map<String, dynamic> toMap() { var map = <String, dynamic>{ colonneNom: nom, colonnePrenom: prenom, colonneAge: age }; if (id != null) { map[colonneId] = id; } return map; } } class PersonneProvider { PersonneProvider._privateConstructor(); static final PersonneProvider instance = PersonneProvider._privateConstructor(); late Database _db; bool _initialized = false; Future<Database> get db async { if (!_initialized) { _db = await _initDatabase(); _initialized = true; } return _db; } Future _initDatabase() async { try { Directory documentsDirectory = await getApplicationDocumentsDirectory(); String path = join(documentsDirectory.path, databaseName); return await openDatabase(path, version: databaseVersion, onCreate: _open); } catch (e) { print("Erreur lors de l'initialisation de la base de données: $e"); rethrow; } } Future _open(Database db, int version) async { await db.execute(''' create table $tablePersonne ( $colonneId integer primary key autoincrement, $colonneNom text, $colonnePrenom text, $colonneAge text) '''); } Future<int> insert(Personne personne) async { Database db = await instance.db; int id = await db.insert(tablePersonne, personne.toMap()); return id; } Future<Personne?> getPersonne(int id) async { Database db = await instance.db; List<Map<String, dynamic>> maps = await db.query(tablePersonne, columns: [colonneId, colonneNom, colonnePrenom, colonneAge], where: '$colonneId = ?', whereArgs: [id]); if (maps.length > 0) { Map<String, dynamic> firstMap = maps.first; return Personne.fromMap({ colonneId: firstMap[colonneId], colonneNom: firstMap[colonneNom], colonnePrenom: firstMap[colonnePrenom], colonneAge: firstMap[colonneAge], }); } return null; } Future<int> delete(int id) async { Database db = await instance.db; return await db.delete(tablePersonne, where: '$colonneId = ?', whereArgs: [id]); } Future close() async => _db.close(); }
PersonnePage.dart
import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'main.dart'; import 'Personne.dart'; import '../CheckboxPage.dart'; void main() => runApp(CheckboxPage()); class PersonnePage extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'SIO', theme: ThemeData.light(), home: const PersonnePageState(title: 'Cours'), ); } } class PersonnePageState extends StatefulWidget { const PersonnePageState({Key? key, required this.title}) : super(key: key); final String title; @override _PersonnePageState createState() => _PersonnePageState(); } class _PersonnePageState extends State<PersonnePage> { @override void initState() { super.initState(); personneEnregistree = Personne.fromMap(mapPersonne); getInstance(); } @override void dispose() { super.dispose(); provider.close(); } final Map<RadioChoix, bool> _mapTransport = { RadioChoix.voiture: true, RadioChoix.avion: false, RadioChoix.bateau: true, }; Map<String, dynamic> mapPersonne = { 'nom': 'DOE', 'prenom': 'John', 'age': '35' }; late Map<String, dynamic> mapPersonneRecuperee; Personne personneEnregistree = Personne(); Personne personneRecuperee = Personne(); late PersonneProvider provider; Future<PersonneProvider> getInstance() async { provider = await PersonneProvider.instance; return provider; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[ const Icon( Icons.account_circle, size: 80.0, color: Colors.blue, ), const Text('Données soumises :'), Container( height: MediaQuery.of(context).size.height * 0.25, width: MediaQuery.of(context).size.width * 0.8, decoration: BoxDecoration( color: Colors.grey, shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(20.0), ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'Nom : ', style: TextStyle(color: Colors.white), ), Text( '${personneEnregistree.toMap()['nom']}', style: const TextStyle( fontSize: 30.0, color: Colors.white, fontWeight: FontWeight.bold), ), ], ), Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'Prénom : ', style: TextStyle(color: Colors.white), ), Text( '${personneEnregistree.toMap()['prenom']}', style: const TextStyle( fontSize: 30.0, color: Colors.white, fontWeight: FontWeight.bold), ), ], ), Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'Age : ', style: TextStyle(color: Colors.white), ), Text( '${personneEnregistree.toMap()['age']}', style: const TextStyle( fontSize: 30.0, color: Colors.white, fontWeight: FontWeight.bold), ), ], ), ], ), ), const Text('Données récupérées :'), Container( height: MediaQuery.of(context).size.height * 0.25, width: MediaQuery.of(context).size.width * 0.8, decoration: BoxDecoration( color: Colors.blue, shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(20.0), ), child: const Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'id : ', style: TextStyle(color: Colors.white), ), Text( '123', style: TextStyle( fontSize: 25.0, color: Colors.white, fontWeight: FontWeight.bold), ), ], ), Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'Nom : ', style: TextStyle(color: Colors.white), ), Text( 'John Doe', style: TextStyle( fontSize: 25.0, color: Colors.white, fontWeight: FontWeight.bold), ), ], ), Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'Prénom : ', style: TextStyle(color: Colors.white), ), Text( 'Jane Doe', style: TextStyle( fontSize: 25.0, color: Colors.white, fontWeight: FontWeight.bold), ), ], ), Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'Age : ', style: TextStyle(color: Colors.white), ), Text( '30', style: TextStyle( fontSize: 25.0, color: Colors.white, fontWeight: FontWeight.bold), ), ], ), ], ), ), Divider(), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ ElevatedButton( onPressed: enregistrer, style: ElevatedButton.styleFrom( foregroundColor: Colors.white, backgroundColor: Colors.green, ), child: Text('Enregistrer'), ), ElevatedButton( onPressed: recuperer, style: ElevatedButton.styleFrom( foregroundColor: Colors.white, backgroundColor: Colors.green, ), child: Text('Lire les données'), ), ElevatedButton( onPressed: supprimer, style: ElevatedButton.styleFrom( foregroundColor: Colors.white, backgroundColor: Colors.green, ), child: const Icon( Icons.delete, color: Colors.white, ), ), ], ), ], ), ), ); } Future<void> enregistrer() async { int id = await provider.insert(personneEnregistree); mapPersonneRecuperee = {'id': id}; personneRecuperee = Personne.fromMap(mapPersonneRecuperee); showDialog( context: context, builder: (BuildContext context) { return const AlertDialog( title: Text('Enregistrement'), content: Text('Les données ont été enregistrées !'), ); }); } Future<void> recuperer() async { if (personneRecuperee.toMap()['id'] != 10) { personneRecuperee = (await provider.getPersonne(personneRecuperee.toMap()['id']))!; setState(() {}); } else { showDialog( context: context, builder: (BuildContext context) { return const AlertDialog( title: Text('Récupération'), content: Text('Aucune donnée à récupérer !'), ); }); } } Future<void> supprimer() async { await provider.delete(personneRecuperee.toMap()['id']); setState(() { personneRecuperee = Personne(); }); showDialog( context: context, builder: (BuildContext context) { return const AlertDialog( title: Text('Suppression'), content: Text('Les données ont été supprimées !'), ); }); } }
解决方法
方案一:将id设为可空类型
修改Personne类,允许id为null,适配未插入数据库的记录状态:
class Personne { int? id; // 改为可空int类型 late String nom; late String prenom; late String age; Personne(); Personne.fromMap(Map<String, dynamic> map) { id = map[colonneId]; // 无id时自动赋值为null,可空类型兼容 nom = map[colonneNom]; prenom = map[colonnePrenom]; age = map[colonneAge]; } Map<String, dynamic> toMap() { var map = <String, dynamic>{ colonneNom: nom, colonnePrenom: prenom, colonneAge: age }; if (id != null) { // 仅当id不为null时,才加入map map[colonneId] = id; } return map; } }
方案二:给id设置默认值
如果不想使用可空类型,可以给id设置默认值(比如0,代表未插入数据库的记录):
class Personne { int id = 0; // 默认值0,区别于自增的正整数id late String nom; late String prenom; late String age; Personne(); Personne.fromMap(Map<String, dynamic> map) { id = map[colonneId] ?? 0; // 无id时使用默认值0 nom = map[colonneNom]; prenom = map[colonnePrenom]; age = map[colonneAge]; } Map<String, dynamic> toMap() { var map = <String, dynamic>{ colonneNom: nom, colonnePrenom: prenom, colonneAge: age }; if (id != 0) { // 仅当id不为0时,才加入map map[colonneId] = id; } return map; } }
额外优化
在PersonnePage的enregistrer方法中,插入数据库后返回的id可以直接赋值给personneEnregistree.id,无需重新创建personneRecuperee:
Future<void> enregistrer() async { int id = await provider.insert(personneEnregistree); personneEnregistree.id = id; // 直接赋值给原实例的id showDialog( context: context, builder: (BuildContext context) { return const AlertDialog( title: Text('Enregistrement'), content: Text('Les données ont été enregistrées !'), ); }); }
内容的提问来源于stack exchange,提问作者Fabien Lebourgeois
相关产品推荐
相关产品推荐

