Flutter migrating to null safety, late or nullable? - json

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

Related

Non nullable instance field must be initalised

im new to Dart-flutter.
i have watching a tutorial video from udemy course writing in dart pad
so i also wrote in dartpad. but it showing error in it.
this is the code wrote in udemy..
import 'dart:convert';
void main(){
var rawJson = '{"url": "https://helo.com","id": 2}';
var parsedJson = json.decode(rawJson);
var imageModel = new ImageModel.fromJson(parsedJson);
print(imageModel.url);
}
class ImageModel{
int id;
String url;
ImageModel.fromJson(parsedJson) {
id = parsedJson['id'];
url = parsedJson['url'];
}
ImageModel(this.id, this.url);
}
in that video it runs, but for me it shows error as
Error compiling to JavaScript:
Info: Compiling with sound null safety
Warning: Interpreting this as package URI, 'package:dartpad_sample/main.dart'.
lib/main.dart:15:3:
Error: This constructor should initialize field 'id' because its type 'int' doesn't allow null.
ImageModel.fromJson(parsedJson) {
^
lib/main.dart:12:7:
Info: 'id' is defined here.
int id;
^^
lib/main.dart:15:3:
Error: This constructor should initialize field 'url' because its type 'String' doesn't allow null.
ImageModel.fromJson(parsedJson) {
^
lib/main.dart:13:10:
Info: 'url' is defined here.
String url;
^^^
Error: Compilation failed.
i have no idea what the problem is..
can you guys help me to troubleshoot the error
Since you're compiling with null safety, you may change to this:
int? id;
String? url;
For further info about null safety, please refer to this link https://dart.dev/null-safety
Try using null safe code:
eg:
int? id;
String? url;
You will try like this
import 'dart:convert';
class ImageModel{
final int id;
final String url;
ImageModel({required this.id, required this.url});
factory ImageModel.fromJson(Map<String, dynamic> parsedJson) {
return ImageModel(
id: parsedJson['id'] as int,
url: parsedJson['url'] as String,
);
}
}
void main() {
var rawJson = '{"url": "https://helo.com","id": 2}';
var parsedJson = json.decode(rawJson);
print(parsedJson['url']);
print(parsedJson['id']);
}
You are probably watching tutorial that was recorded before null-safety introduction in Dart (null-safety is a Dart feature that helps prevent NullRefference exceptions in runtime).
You can either go with nullable values (like #Sowat Kheang suggested in another answer) for id and url in ImageModel class (change String to String? and int to int?). Then you will be fine, but it defeats the purpose of null-safety.
Or you can change your code so it will handle null values in proper way. Code will look something like this:
import 'dart:convert';
void main(){
var rawJson = '{"url": "https://helo.com","id": 2}';
var parsedJson = json.decode(rawJson);
var imageModel = ImageModel.fromJson(parsedJson);
print(imageModel.url);
}
class ImageModel{
// Make your fields 'final' if you don't plan to change them later
// this will make your life easier and is a good practice.
final int id;
final String url;
// we add curly braces to make arguments named
// and add 'required' keyword to indicate that constructor can't be called
// without actually passing arguments into it
ImageModel({required this.id, required this.url});
// here we use factory constructor that will call our default constructor
// it's also a good practice to specify type of a variable if you know it (Map<String, dynamic> in this case)
factory ImageModel.fromJson(Map<String, dynamic> parsedJson) {
// since we don't know for sure if 'parsedJson' has key 'id'
// we would add '??' — null checking operator.
// So if parsedJson['id'] is null, value -1 will be passed
// to the constructor. Same with 'parsedJson['url']'.
return ImageModel(
id: parsedJson['id'] ?? -1,
url: parsedJson['url'] ?? '');
}
}
var rawJson = '{"url": "https://helo.com","id": 2}';
Actually it is a Sting Json and Encoding is not perform properly So follow the step..
Map data={"url": "https://helo.com","id": 2};
Encode that data. json.encode(data);
Now the Encoding is done then you can send the data any server if you have..
When you get back data then... json.decode(rawData)
Then parsing work properly

dart/Flutter: Problems with Decodeing json with utf8 decode

I try to load a json-file to put it in a filterable/searchable Listview (search for a diagnosis with a symptom). I'm new to in programming so probably there is a better / simpler way to do this but i would like to do it this way, so it doesnt get more complicated.
I get this error if i try to use utf8.decode:
"The argument type 'String' can't be assigned to the parameter type 'List'."
This is what i tried:
class Services {
static Future<List<User>> getUsers() async {
final response = await rootBundle.loadString('assets/diff.json');
List<User> list = parseUsers(response);
return list;
}
static List<User> parseUsers(String responseBody) {
final parsed = json.decode(utf8.decode(responseBody)).cast<Map<String, dynamic>>();
return parsed.map<User>((json) => User.fromJson(json)).toList();
}
}
the User Class:
class User {
String symptom;
String diagnosis;
User(this.symptom, this.diagnosis);
User.fromJson(Map<String, dynamic> json){
symptom = json['symptom'];
diagnosis = json['diagnosis'];
}
}
extract of the json file:
[
{"symptom":"Kopfschmerz","diagnosis":"Migräne, Spannungskopfschmerz"}
,
{"symptom":"Bauchschmerz","diagnosis":"Apendizitis, Infektion"}
]
Is there a simple way to make this work? Thanks!
With dynamic json.decode(String) the returned object can have a real type of:
List<dynamic>
Map<String, dynamic>
But also when the type is List<dynamic>, decode has parsed also the items in the List, so in your case (since your json has structure [{"" : ""}]) you just need to cast the (reified) List's type parameter with the cast() method.
static List<User> parseUsers(String responseBody) {
//final parsed = json.decode(utf8.decode(responseBody)).cast<Map<String, dynamic>>();
final parsed = (json.decode(responseBody) as List<dynamic>).cast<Map<String, dynamic>>();
return parsed.map<User>((json) => User.fromJson(json)).toList();
}

Dynamic List in Flutter for Json

I'm working with some complex json in dart, and I have an issue creating objects before I know what type they'll be.
I appreciate the suggestions, but I don't think I completely understand. In the given answer:
var entity = Model();
castToEntity(entity, {'test': 10});
Don't I need to know that it will be a Model class?
What if I have the below two classes:
#JsonSerializable(explicitToJson: true, includeIfNull: false)
class Location {
String id;
String resourceType;
Location({#required this.id, this.resourceType})
factory Location.fromJson(Map<String, dynamic> json) => _$LocationFromJson(json);
Map<String, dynamic> toJson() => _$LocationToJson(this);
}
class Reference {
String reference;
String resourceType;
Location({#required this.reference, this.resourceType}
factory Reference.fromJson(Map<String, dynamic> json) => _$ReferenceFromJson(json);
Map<String, dynamic> toJson() => _$ReferenceToJson(this);
}
And then I query the server, and I don't know what kind of class it will be. It could be a Location, or a Reference, or if it's a list, it could be multiple of both, and I don't know until I've requested it.
var myBundle = Bundle.fromJson(json.decode(response.body));
Each "myBundle.entry" is another resource. I'd like to be able to use information from that resource to define itself. So I could do something like:
myBundle.entry.resourceType newResource = new myBundle.entry.resourceType();
What I'm doing right now is sending it to a function that has all of the possible options predefined:
var newResource = ResourceTypes(myBundle.entry[i].resource.resourceType,
myBundle.entry[i].resource.toJson());
dynamic ResourceTypes(String resourceType, Map<String, dynamic> json) {
if (resourceType == 'Location') return (new Location.fromJson(json));
if (resourceType == 'Reference') return (new Reference.fromJson(json));
}
It was said that there's not reflection in dart, so I didn't know any other way to do it.
As far as I know, it's not possible since Dart doesn't have Reflection like c#, the most close that I can imagine, is using an abstract class that enforces your entity to implement fromJson, and, in that method, you read the Map and put values into fields, like the code below:
abstract class Serializable {
void fromJson(Map<String,dynamic> data);
}
class Model implements Serializable {
int test;
#override
void fromJson(data) {
test = data['test'];
}
}
Serializable castToEntity(Serializable entity, Map<String, dynamic> data) {
return entity..fromJson(data);
}
Now, when you read you database and have the Map, you can call a method generic like:
var entity = Model();
castToEntity(entity, {'test': 10});
print(entity.test);
Where entity is an empty model.
Note: Your fields on entity, cannot be final, since fromJson is an instance method and not a factory method.

How to implement Singleton pattern in Dart using factory constructors?

I'm trying to implement the singleton pattern in a database helper class, but, I can't seem to understand the purpose of a factory constructor and if there's an alternative method to using it.
class DbHelper {
final String tblName ='';
final String clmnName ='';
final String clmnPass='';
DbHelper._constr();
static final DbHelper _db = new DbHelper._constr();
factory DbHelper(){ return _db;}
Database _mydb;
Future<Database> get mydb async{
initDb() {
if(_mydb != null)
{
return _mydb;
}
_mydb = await initDb();
return _mydb;
}
There is no need to use the factory constructor.
The factory constructor was convenient when new was not yet optional because then it new MyClass() worked for classes where the constructor returned a new instance every time or where the class returned a cached instance. It was not the callers responsibility to know how and when the object was actually created.
You can change
factory DbHelper(){ return _db;}
to
DbHelper get singleton { return _db;}
and acquire the instance using
var mySingletonReference = DbHelper.singleton;
instead of
var mySingletonReference = DbHelper();
It's just a matter of preference.
I also found this article helpful: https://theburningmonk.com/2013/09/dart-implementing-the-singleton-pattern-with-factory-constructors/

WCF restful returning JSON by using Entity Framework Complex

recently I have set up a WCF restful service with EF4.
It all worked out when returning XML format response. however when it comes to JSON, I got 504 Error. unable to return json data, WCF Resful Service .NET 4.0
By digging deeper by using Service Trace Viewer:
I found this error:
'The type 'xxx.DataEntity.AppView'
cannot be serialized to JSON because
its IsReference setting is 'True'. The
JSON format does not support
references because there is no
standardized format for representing
references. To enable serialization,
disable the IsReference setting on the
type or an appropriate parent class of
the type.'
The "AppView" is a complex object class which generated by EF4 from a store procedure.
I spend quite a bit time google how to disable the IsReference, very little result so far.
anyone? with any solutions?
thanks in advance
Code:
[OperationContract]
[WebInvoke(Method = "GET",
BodyStyle = WebMessageBodyStyle.Wrapped,
UriTemplate = "App/{id}/{format}")]
AppView FuncDetail(string id, string format);
public AppView FuncDetail(string id, string format)
{
SetResponseFormat(format);
return AppSvcs.GetById(id);
}
private void SetResponseFormat(string format)
{
if (format.ToLower() == "json")
{
ResponseContext.Format = WebMessageFormat.Json;
}
else
{
ResponseContext.Format = WebMessageFormat.Xml;
}
}
I ran into exactly the same issue. It only happened on one of my service methods where I was trying to return JSON serialised Entity objects. For all my other methods I was returning JSON serialised data transfer objects (DTOs), which are stand-alone and not connected to the Entity framework. I am using DTOs for data posted into methods. Often, the data you send out does not need all the data you store in the model or the database e.g. ID values, updated dates, etc. The mapping is done in the model class, like so:
public partial class Location
{
public static LocationDto CreateLocationDto(Location location)
{
LocationDto dto = new LocationDto
{
Accuracy = location.Accuracy,
Altitude = location.Altitude,
Bearing = location.Bearing
};
return dto;
}
It may seem a bit clunky but it works and it ensures that you only send the data fields you intended to send back. It works for me because I only have 5 or 6 entities but I can see that it would get a bit tedious if you have lots of classes.
I was running into the same problem, as caused by using the auto-generated ADO Entity Models. I have not found a direct fix for this issue, but as a work around, I serialize the response as json explicitly.
So in your example, the AppView FuncDetail looks like this:
public object FuncDetail(string id, string format)
{
SetResponseFormat(format);
// where AppSvc is the object type and the enumerable list of this type is returned by the GetById method, cast it to a json string
return JSONSerializer.ToJson<AppSvc>(AppSvcs.GetById(id));
}
Here are the Serializers that I'm using:
public static class GenericSerializer
{
public static DataTable ToDataTable<T>(IEnumerable<T> varlist)
{
DataTable dtReturn = new DataTable();
// column names
PropertyInfo[] oProps = null;
if (varlist == null) return dtReturn;
foreach (T rec in varlist)
{
// Use reflection to get property names, to create table, Only first time, others will follow
if (oProps == null)
{
oProps = ((Type)rec.GetType()).GetProperties();
foreach (PropertyInfo pi in oProps)
{
Type colType = pi.PropertyType;
if ((colType.IsGenericType) && (colType.GetGenericTypeDefinition()
== typeof(Nullable<>)))
{
colType = colType.GetGenericArguments()[0];
}
dtReturn.Columns.Add(new DataColumn(pi.Name, colType));
}
}
DataRow dr = dtReturn.NewRow();
foreach (PropertyInfo pi in oProps)
{
dr[pi.Name] = pi.GetValue(rec, null) == null ? DBNull.Value : pi.GetValue
(rec, null);
}
dtReturn.Rows.Add(dr);
}
return dtReturn;
}
}
public static class JSONSerializer
{
public static string ToJson<T>(IEnumerable<T> varlist)
{
DataTable dtReturn = GenericSerializer.ToDataTable(varlist);
return GetJSONString(dtReturn);
}
static object RowsToDictionary(this DataTable table)
{
var columns = table.Columns.Cast<DataColumn>().ToArray();
return table.Rows.Cast<DataRow>().Select(r => columns.ToDictionary(c => c.ColumnName, c => r[c]));
}
static Dictionary<string, object> ToDictionary(this DataTable table)
{
return new Dictionary<string, object>
{
{ table.TableName, table.RowsToDictionary() }
};
}
static Dictionary<string, object> ToDictionary(this DataSet data)
{
return data.Tables.Cast<DataTable>().ToDictionary(t => "Table", t => t.RowsToDictionary());
}
public static string GetJSONString(DataTable table)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
return serializer.Serialize(table.ToDictionary());
}
public static string GetJSONString(DataSet data)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
return serializer.Serialize(data.ToDictionary());
}}
It is a lot clearer to use Entity Metadata instead of Reflection.
The Metadata is pretty extensive.
another way to do this is to use LINQ to create an anonymous type with the subset of fields that you need from your entity and then use JSON.NET to serialize the collection of anon types that you created in the LINQ statement. then persist that collection out as a string by serializing.