Flutter - How to parse JSON data to a base class? - dart

I'm wondering how can I parse a JSON data to a base class, because I'm trying to write, but get Unhandled Exception: type 'Future' is not a subtype of type 'WeatherModel' in type cast.
Here is my code:
abstract class BaseModel {
fromJson(Map<String, dynamic> json);
Map<String, dynamic> toJson();
}
import 'BaseModel.dart';
class WeatherModel extends BaseModel {
String success;
Result result;
Records records;
WeatherModel({this.success, this.result, this.records});
#override
fromJson(Map<String, dynamic> json) {
//...
}
#override
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
//... parse to Json
return data;
}
}
}
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:flightinfo/model/BaseModel.dart';
class HttpUtils {
static Future<BaseModel> get(String url, Map<String, dynamic> params, BaseModel baseModel) async {
try {
print("url:$url,params:$params");
Response response = await Dio().get(url, queryParameters: params);
if (response != null && response.statusCode == 200) {
baseModel.fromJson(response.data);
print(baseModel);
return baseModel;
}
print(response);
} catch (exception) {
print(exception);
}
return null;
}
}
class WeatherRequest {
Future<WeatherModel> get() async {
return HttpUtils.get(_url, _params,new WeatherModel());
}
}
I think HttpUtils.get direct to BaseModel for extensibility. This is very common in Java, but in Dart I get a exception in WeatherRequest in below line:
return HttpUtils.get(_url, _params,new WeatherModel());
Unhandled Exception: type 'Future' is not a subtype of type 'WeatherModel' in type cast
In Dart, how to take care of this cast?

The error might be occurring because get() method of WeatherRequest returns a Future to WeatherModel but you have returned a Future<BaseModel> instead.
Try adding await to resolve Future converting it to BaseModel before returning.
return (await HttpUtils.get(_url, _params,new WeatherModel()) );
Also, as per this SO answer:
You can cast an object to its parent class, but you can't cast an object to any of its child class.
So implicit casting of BaseModel to WeatherModel may not work as expected. Instead, you can add a helper method to BaseModel like BaseModel.toWeatherModel and return a WeatherModel
abstract class BaseModel {
toWeatherModel(){
//...
}
}
Or modify get method of WeatherRequest to return Future<BaseModel>.
class WeatherRequest {
Future<BaseModel> get() async {
return (await HttpUtils.get(_url, _params,new WeatherModel()));
}
}

Related

Is this factory a bad practice?

I'm working for a generic CRUD Repository but I have some problems with the serialization of my classes. For solving this issue I have create a factory on my base Entity:
const factories = {
Product: Product.fromJson,
};
abstract class Entity {
[...]
factory Entity.fromJson({
required Type type,
required Map json,
}) {
final factory = factories[type];
if (factory == null) {
throw Exception('Type Error: ($type) not found on factories');
}
return factory(json: json);
}
Map get json => {'id': id};
}
And in my repository abraction I just use this factory:
abstract class IDioCrudRepository<E extends Entity> extends DioRepository {
IDioCrudRepository({required super.baseUrl});
FutureOr<E> get(String id) async {
final res = await dio.get('$baseUrl/$id');
return Entity.fromJson(
type: E,
json: res.data as Map,
) as E;
}
[...]
}
Dou you know guys if this is a bad practice?
I would suggest you , at least, to make a Builder class and to move all that code far from Entity. In this manner entity class would not have any responsibility on how to build itself from a key and a json map.
const factories = {
Product: Product.fromJson,
};
class EntityBuilder {
static Entity fromJson({
required Type type,
required Map json,
}) {
final factory = factories[type];
if (factory == null) {
throw Exception('Type Error: ($type) not found on factories');
}
return factory(json: json);
}
}
abstract class Entity {
[...]
Map get json => {'id': id};
}
abstract class IDioCrudRepository<E extends Entity> extends DioRepository {
IDioCrudRepository({required super.baseUrl});
FutureOr<E> get(String id) async {
final res = await dio.get('$baseUrl/$id');
return EntityBuilder.fromJson(
type: E,
json: res.data as Map,
) as E;
}
[...]
}

How to <T extend BaseClass> where BaseClass has a factory function?

Following is a simple class that provides a few helper functions for reading and writing data.
class BaseDAO<T> {
final String _modelName;
static late final StoreRef<int, Map<String, Object?>> _store;
BaseDAO(this._modelName) {
_store = intMapStoreFactory.store(_modelName);
}
Future<Database> get _db async => await AppDatabase().database;
Future<void> create(T object) async {
await _store.add(await _db, object.toJSON()); //The method 'toJSON' can't be unconditionally invoked because the receiver can be 'null'.
}
}
Now the issue with this is that the generic type T doesn't have toJSON function. I tried fixing this by writing an abstract class.
abstract class BaseModel {
Map<String, dynamic> toJSON();
factory BaseModel.fromJson(Map<String, dynamic> json);
}
and extending T with BaseModel. This presents all kinds of issues one of them being that I am unable to write an abstract class.
Any solution will be greatly appreciated.
In your case T object is empty. You need to extend it to some object which provides a method toMap().
Example:
class BaseDAO<T extends BaseModel> {
final String _modelName;
static late final StoreRef<int, Map<String, Object?>> _store;
BaseDAO(this._modelName) {
_store = intMapStoreFactory.store(_modelName);
}
Future<Database> get _db async => await AppDatabase().database;
Future<void> create(T object) async {
await _store.add(await _db, object.toMap()); // <- Dart see that this object extends to BaseModel and has a method `toMap()`,
}
}
abstract class BaseModel {
Map<String, dynamic> toMap();
}
class User extends BaseModel {
User({this.name});
final String? name;
#override
Map<String, dynamic> toMap() => {'name': name};
}
Future<void> create() async {
final user = User(name: 'Superman');
final base = BaseDAO('ModelName');
await base.create(user); // <- The user object will be added as `Map`.
}

How do I create an abstract factory?

Is it possible to somehow create an abstract factory method? Maybe what I'm trying to do is possible to implement differently?
abstract class ApiModel {
// Error: A function body must be provided.
factory ApiModel.fromJson(Map<String, dynamic> json);
}
class User extends ApiModel {
final int id;
final String name;
User({required this.id, required this.name});
#override
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
);
}
}
class ApiResponse<Model extends ApiModel> {
final List<Model> results;
ApiResponse({required this.results});
factory ApiResponse.fromJson(Map<String, dynamic> json) {
return ApiResponse(results: (json['results'] as List).map((item) => Model.fromJson(item)).toList());
}
}
I solved it like this:
factory ApiResponse.fromJson(Map<String, dynamic> json, Model Function(dynamic) mapper) {
return ApiResponse(
info: Info.fromJson(json['info']),
results: (json['results'] as List).map(mapper).toList(),
);
}

What is an equivalent for Dart 2 to `typeof` of TypeScript?

I'm new to Dart 2. I want a class to have a property. It's a reference of other class. it's not an instance but class itself. In TypeScript, it's possible to write as below. Is there a same way in Dart 2?
class Item { }
class ItemList {
itemClass: typeof Item;
}
const itemList = new ItemList();
itemList.itemClass = Item;
UPDATED:
I added some more context. The following is minimal sample code. I want to delegate a role of instantiation to super class.
class RecordBase {
id = Math.random();
toJson() {
return { "id": this.id };
};
}
class DbBase {
recordClass: typeof RecordBase;
create() {
const record = new this.recordClass();
const json = record.toJson();
console.log(json);
}
}
class CategoryRecord extends RecordBase {
toJson() {
return { "category": "xxxx", ...super.toJson() };
};
}
class TagRecord extends RecordBase {
toJson() {
return { "tag": "yyyy", ...super.toJson() };
};
}
class CategoryDb extends DbBase {
recordClass = CategoryRecord;
}
class TagDb extends DbBase {
recordClass = TagRecord;
}
const categoryDb = new CategoryDb();
categoryDb.create();
const tagDb = new TagDb();
tagDb.create();
I have tried to make you sample code into Dart. As I told before, you cannot get a reference to a class and call the constructor on runtime based on this reference.
But you can make a reference to a method which constructs the object of you class.
import 'dart:math';
class RecordBase {
static final Random _rnd = Random();
final int id = _rnd.nextInt(100000);
Map<String, dynamic> toJson() => <String, dynamic>{'id': id};
}
abstract class DbBase {
final RecordBase Function() getRecordClass;
RecordBase record;
Map<String, dynamic> json;
DbBase(this.getRecordClass);
void create() {
record = getRecordClass();
json = record.toJson();
print(json);
}
}
class CategoryRecord extends RecordBase {
#override
Map<String, dynamic> toJson() {
return <String, dynamic>{'category': 'xxxx', ...super.toJson()};
}
}
class TagRecord extends RecordBase {
#override
Map<String, dynamic> toJson() {
return <String, dynamic>{'tag': 'yyyy', ...super.toJson()};
}
}
class CategoryDb extends DbBase {
CategoryDb() : super(() => CategoryRecord());
}
class TagDb extends DbBase {
TagDb() : super(() => TagRecord());
}
void main() {
final categoryDb = CategoryDb();
categoryDb.create(); // {category: xxxx, id: 42369}
final tagDb = TagDb();
tagDb.create(); // {tag: yyyy, id: 97809}
}
I am not really sure if the create() method should be seen as a method or a constructor. So I choose to make it a method to be closer to your code.

Flutter and Firestore: NoSuchMethodError: The method '...' was called on null

i have a login page in my flutter app to authenticate and register users. When i want to save the user email-address to firestore (after register), i get the error:
flutter: Error: NoSuchMethodError: The method 'saveRegisterUserData' was called on null.
Receiver: null
Tried calling: saveRegisterUserData(email: "test#test.de", userID: "EVDnVFMkYeYd291Yzu2j2r8HLpC2")'
i have created a seperate dart-file for the Firestore Functions. It look like this:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:async';
abstract class BaseFire {
Future <void> saveRegisterUserData({String userID, String email});
}
class Fire implements BaseFire {
final Firestore firestore = new Firestore();
DocumentReference usersdocuments;
Future <void> saveRegisterUserData({String userID, String email}) async {
usersdocuments = Firestore.instance.collection("Users").document(userID);
Map<String, String> registerdata = <String, String>{
"Email": email,
};
usersdocuments.setData(registerdata).whenComplete(() {
return print("Document Added");
}).catchError((error) => print(error));
}
}
i import this file in my login page und execute this function:
import '../Firebase/firestore.dart';
class SignInPage extends StatefulWidget {
SignInPage({this.firestore});
final BaseFire firestore;
#override
State<StatefulWidget> createState() {
return _SignInPageState();
}
}
class _SignInPageState extends State<SignInPage> {
String _email;
String _password;
...
void submitRegister() async {
if (validateAndSave()) {
try {
String userId = await widget.auth.createUserWithEmailAndPassword(_email, _password);
await widget.firestore.saveRegisterUserData(userID: userId, email: _email);
} catch (error) {
print(error);
}
}
}
}
Where is my mistake? Thank you for our help
You have Automatic Class Member Variable Initialization at
SignInPage({this.firestore});
{} make the parameter optional and unless you truly mean to make your
firebase //variable
optional or have default value don't enclose it in {} just remove it. You will get any error message where you have used your SignInPage,just fix the parameter and hopefully things will work fine.

Resources