Search code examples
jsonflutterhttpsqflitedart-null-safety

Flutter migrating to null safety, late or nullable?


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:

  1. Parsing from JSON to dart
  2. Initialising variables
  3. Passing data from MaterialPageRoute
  4. http database helper class
  5. 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 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!


Solution

  • 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!