Flutter migrating to null safety, late or nullable?
To answer my own question, or questions... Disclaimer: I have no Dart errors after making the changes below but my code isn't exactly working so the solution might not be 100% correct.
Case 1: Parsing from JSON to dart
Since quicktype is null-safe yet, I have opted for json_serializable do my JSON to dart conversion.
Case 2: Initialising variables
Instead of declaring <FutureList<List>> as nullable, I have added a type to my FutureBuilder.
child: FutureBuilder<List<VocabList>>(
Case 3: Passing data from MaterialPageRoute
I no longer have to declare the variables as nullable.
Case 4: http database helper class
Did not use the nullable constructor but instead used late
.
late
String someName;
Case 5: sqflite database helper class
New code here:
// Initialise the database.
// Only allow a single open connection to the database.
static Database? _database; // Added ? for null-safety
Future<Database> get database async {
if (_database != null)
return _database as Future<Database>; // Added 'as type' for null-safety
_database = await _initDatabase();
return _database as Future<Database>; // Added 'as type' for null-safety
}
If anybody spots any errors or knows how I can do this better, please let me know!
keymistress
Updated on December 31, 2022Comments
-
keymistress over 1 year
I would like to migrate to null safety correctly but I am not sure exactly when to use late and when to use nullable. I have read Flutter's Understanding null safety article and finished the Null Safety codelabs but I don't know if I have understood it correctly. New to Flutter and Dart, trying to catch this null safety migration curve ball here in five different scenarios:
- Parsing from JSON to dart
- Initialising variables
- Passing data from MaterialPageRoute
- http database helper class
- sqflite database helper class
Case 1: Parsing from JSON to dart
I am fetching data with http from a MySQL database and using quicktype.io to parse JSON to dart. The non null-safety code of my data model looks like this:
// To parse this JSON data, do // // final someList = someListFromJson(jsonString); import 'dart:convert'; List<SomeList> someListFromJson(String str) => List<SomeList>.from(json.decode(str).map((x) => SomeList.fromJson(x))); String someListToJson(List<SomeList> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson()))); class SomeList { SomeList({ this.someId, this.somebody, }); String someId; String somebody; factory SomeList.fromJson(Map<String, dynamic> json) => SomeList( someId: json["someId"], somebody: json["somebody"], Map<String, dynamic> toJson() => { "someId": someId, "somebody": somebody, }; }
Using
dart migrate
, they suggested that IChanged type 'String' to be nullable
.However, I know for a fact though that in my json data,
someId
is never and cannot be null, whilesomebody
could be null. Should I still use the?
nullable type for the sake of initialising? My understanding is that I should not use the!
null assertion operator forsomebody
since it technically does not have a value yet. Well, then does that mean I should use thelate
keyword instead?String? someId; String? somebody;
or
late String someId; late String somebody;
Case 2: Initialising variables
I call
SomeList
in a Stateful widget on one of my screens as a Future.Old code:
class _SomeScreenState extends State<SomeScreen > { Future<List<VocabList>> futureList;
Once again it is proposing that I make it nullable, like so:
Future<List<VocabList > >? futureList;
Am I right to understand that I use the
?
nullable type for initialisingFuture<List<VocabList>>
?Case 3: Passing data from MaterialPageRoute
I am passing data from MaterialPageRoute as such:
MaterialPageRoute( builder: (context) => SomeScreen( someId: something[index].someId, somebody: something[index].somebody,
On the receiving end, the old code looks like this:
class SomeScreen extends StatefulWidget { final String someId; final String somebody; SomeScreen({ Key? key, @required this.someId, @required this.somebody,
Again it is recommending I set my two final variables
someId
andsomebody
as nullable but should they be nullable or are they just late?Should I do this?
class SomeScreen extends StatefulWidget { final String? someId; final String? somebody; SomeScreen({ Key? key, @required this.someId, @required this.somebody,
or
class SomeScreen extends StatefulWidget { late final String someId; late final String somebody; SomeScreen({ Key? key, @required this.someId, @required this.somebody,
Case 4: http database helper class
I am passing the variable
someName
with a button to a http request.import 'dart:io'; import 'package:http/http.dart' as http; import 'package:test/models/something_list.dart'; List<SomethingList > _list = []; String someName; class Somebody { static const String url = 'http://localhost/~idunno/api_search.php'; static Future<List<SomeList > > getData(String someName) async { try { http.Response response = await http.get(Uri.parse(url + '?q=' + someName));
someName
cannot be null or else the http request will fail. Should I still declare it as nullable and handle the failure usingon FormatException
like so?List<SomethingList > _list = []; String? someName; // some code omitted on FormatException { throw InvalidFormatException('Something is not right here');
Case 5: sqflite database helper class
Old code:
static Database _database; Future<Database> get database async { if (_database != null) return _database; // lazily instantiate the db the first time it is accessed _database = await _initDatabase(); return _database; } // some code omitted Future<bool > checkBookmark(String someId) async { Database db = await instance.database; var bookmarked = await db.query(table, where: 'someId = ?', whereArgs: [someId]); return bookmarked.isEmpty ? false : true; }
Two questions here: (1) Like the above-mentioned scenarios, do I make
Database
andFuture<Database>
nullable because of initialisation? (2) What doesAdded a cast to an expression (non-downcast)
mean?Suggested null safety changes:
static Database? _database; Future<Database?> get database async { if (_database != null) return _database; // lazily instantiate the db the first time it is accessed _database = await _initDatabase(); return _database; } // some code omitted Future<bool > checkBookmark(String? someId) async { Database db = await (instance.database as FutureOr<Database>); var bookmarked = await db.query(table, where: 'someId = ?', whereArgs: [someId]); return bookmarked.isEmpty ? false : true; }
Any help on this will be much appreciated and I hope the answers will help other new coders with migrating to null safety as well!