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:
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 I Changed type 'String' to be nullable
.
However, I know for a fact though that in my json data, someId
is never and cannot be null, while somebody
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 for somebody
since it technically does not have a value yet. Well, then does that mean I should use the late
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 initialising Future<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
and somebody
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 using on 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
and Future<Database>
nullable because of initialisation? (2) What does Added 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!
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!