Inheritance in Null Safety - Flutter - flutter

I'm working on Flutter Clean Architecture. Initially I developed my project without sound null-safety. I recently migrated it to null-safety.
Migrating to null-safety was pretty smooth but unfortunately there is an issue with inheriting classes that I'm unable to resolve.
Here is a sample of my code I'm working with:
ENTITIES:
UserEntity:
class UserEntity extends Equatable {
final int userID;
final String email;
const UserEntity({required this.userID, required this.email});
#override
List<Object> get props => [userID, email];
}
CourseEntity:
class CourseEntity extends Equatable {
final int courseID;
final String courseName;
const CourseEntity({required this.courseID, required this.courseName});
#override
List<Object> get props => [courseID, courseName];
}
DataEntity:
class DataEntity extends Equatable {
final UserEntity? user;
final CourseEntity? course;
const DataEntity({required this.user, required this.course});
#override
List<Object> get props => [user, course];
}
MODELS:
UserModel:
class UserModel extends UserEntity {
const UserModel({
required int userID,
required String email,
}) : super(userID: userID, email: email);
factory UserModel.fromJson(Map<dynamic, dynamic> json) {
return UserModel(
userID: json["userID"],
email: json["email"],
);
}
Map<String, dynamic> toJson() {
return {
"userID": userID,
"email": email,
};
}
}
CourseModel:
class CourseModel extends CourseEntity {
const CourseModel({
required int courseID,
required String courseName,
}) : super(courseID: courseID, courseName: courseName);
factory CourseModel.fromJson(Map<dynamic, dynamic> json) {
return CourseModel(
courseID: json["courseID"],
courseName: json["courseName"],
);
}
Map<String, dynamic> toJson() {
return {
"courseID": courseID,
"courseName": courseName,
};
}
}
DataModel:
class DataModel extends DataEntity {
const DataModel({
required UserModel? user,
required CourseModel? course,
}) : super(user: user, course: course);
factory DataModel.fromJson(Map<dynamic, dynamic> json) {
return DataModel(
user: UserModel.fromJson(json["user"]),
course: CourseModel.fromJson(json["course"]),
);
}
}
LOCAL DATASOURCE:
Saving Data Locally:
abstract class LocalDataSource {
Future<void> saveData(DataModel model);
Future<void> saveUser(UserModel user);
Future<void> saveCourse(CourseModel course);
}
class LocalDataSourceImpl implements LocalDataSource {
#override
Future<void> saveData(DataModel model) async {
await saveUser(model.user);
await saveCourse(model.course);
}
#override
Future<void> saveUser(UserModel model) async {
final box = await Hive.openBox("USER");
return box.put("user", model.toJson());
}
#override
Future<void> saveCourse(CourseModel model) async {
final box = await Hive.openBox("COURSE");
return box.put("course", model.toJson());
}
}
Explanation:
I hope all the sample classes are easy to understand.
There are 2 entities User and Course. The Data entity is a combination of these 2 entities. The models are extending these entities which means that the entities are the parent classes for their respective models.
The class LocalDataSource is used to store data locally on the device. I'm using Hive to cache the data. There are 2 main futures namely saveUser and saveCourse. The saveData future is the combination of these 2 futures just like the entities.
Problem:
The problem exists in the future saveData of class LocalDataSourceImpl. The future requires 1 argument i.e. DataModel. The other futures also require 1 argument i.e. UserModel and CourseModel.
In saveData(model) future, when I provide the values for respective arguments to saveUser and saveCourse as saveUser(model.user) and saveCourse(model.course), this is the error that I receive on arguments passed:
The argument type 'UserEntity' can't be assigned to the parameter type 'UserModel'.dart(argument_type_not_assignable)
Workarounds:
First workaround could be using getters:
class DataModel extends DataEntity {
final UserModel? _user;
final CourseModel? _course;
const DataModel({
required UserModel user,
required CourseModel course,
}) : _user = user,
_course = course, super(
user: user,
course: course,
);
UserModel? get user => _user;
CourseModel? get course => _course;
...
}
Second workaround could be overriding fields:
class DataModel extends DataEntity {
final UserModel? user;
final CourseModel? course;
const DataModel({
required this.user,
required this.course,
}) : super(
user: user,
course: course,
);
...
}
I'm confused if any of these 2 is a good practice, because there must be no assignment error as Models are actually the subclasses of Entities.
Thanks in advance!

Simplest and Easiest way is to add type cast, and it works great!
class LocalDataSourceImpl implements LocalDataSource {
#override
Future<void> saveData(DataModel model) async {
await saveUser(model.user as UserModel);
await saveCourse(model.course as CourseModel);
}
...

Related

Riverpod Model from List<Model> to Map<String>

I am new to flutter and I am a bit confused about Riverpod and have wasted a few days on this issue which is probably really easy. I have a Model, Provider and Service created with Riverpod which I will share below. I have a widget that takes a Map and an API that is structured
{
"job": [
{"feild1": "data",..},
{"feild2": "data",..},
{"feild3": "data",..}
]
}
It is being mapped as List how can I change that to Map for a child widget I have created.
This is my Provider:
final jobsDataProvider = FutureProvider<List<JobsModel>>((ref) async {
return ref.watch(jobsProvider).getJobs();
});
This is my model:
class JobsModel {
final String jobid;
final String from_state;
final String from_suburb;
final String to_state;
final String to_suburb;
final String travel_time;
final String date;
final String distance;
final String status;
final String map;
JobsModel({
required this.jobid,
required this.from_state,
required this.from_suburb,
required this.to_state,
required this.to_suburb,
required this.travel_time,
required this.date,
required this.distance,
required this.status,
required this.map,
});
factory JobsModel.fromJson(Map<String, dynamic> json) {
return JobsModel(
jobid: json['jobid'],
from_state: json['from']['state'],
from_suburb: json['from']['suburb'],
to_state: json['to']['state'],
to_suburb: json['to']['suburb'],
travel_time: json['travel_time'],
date: json['date'],
distance: json['distance'],
status: json['status'],
map: json['map'],
);
}
}
This is my service:
class ApiServices {
String endpoint = 'https://localhost:3000/jobs';
Future<List<JobsModel>> getJobs() async {
Response response = await get(Uri.parse(endpoint));
if (response.statusCode == 200) {
final List result = jsonDecode(response.body)['jobs'];
return result.map(((e) => JobsModel.fromJson(e))).toList();
} else {
throw Exception(response.reasonPhrase);
}
}
}
final jobsProvider = Provider<ApiServices>((ref) => ApiServices());
My child widget takes a Map<String, dynamic> how can I make this work so I can map multiple widgets from the returned api call into a row.
Thanks heaps all.

A value of type 'Iterable<HospitalListModel>' can't be assigned to a variable of type 'List<HospitalListModel>'

I got a flutter error A value of type 'Iterable<HospitalListModel>' can't be assigned to a variable of type 'List<HospitalListModel>'. This is my model:
List<HospitalListModel> hospitalListModelFromJson(String str) => List<HospitalListModel>.from(json.decode(str).map((x) => HospitalListModel.fromJson(x)));
String hospitalListModelToJson(List<HospitalListModel> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class HospitalListModel {
HospitalListModel({
this.id,
this.title,
this.content,
this.image,
this.phone,
this.coordinates,
this.website,
this.createdAt,
this.updatedAt,
});
dynamic id;
dynamic title;
dynamic content;
dynamic image;
dynamic phone;
dynamic coordinates;
dynamic website;
dynamic createdAt;
dynamic updatedAt;
factory HospitalListModel.fromJson(Map<String, dynamic> json) => HospitalListModel(
id: json["id"],
title: json["title"],
content: json["content"],
image: json["image"],
phone: json["phone"],
coordinates: json["coordinates"],
website: json["website"],
createdAt: json["created_at"],
updatedAt: json["updated_at"],
);
Map<String, dynamic> toJson() => {
"id": id,
"title": title,
"content": content,
"image": image,
"phone": phone,
"coordinates": coordinates,
"website": website,
"created_at": createdAt.toIso8601String(),
"updated_at": updatedAt.toIso8601String(),
};
}
and this is where the error come from, it's from the API provider and im confused why it throw iterable
class ApiProvider {
final Dio _dio = Dio();
final String _url = 'http://lovemonster.my.id/hospital';
Future<List<HospitalListModel>> fetchHospitalList() async {
try {
List<HospitalListModel> hospitalList = [];
Response response = await _dio.get(_url);
var mData = response.data as List;
hospitalList = mData.
map<HospitalListModel>((e) => hospitalListModelFromJson(e)
.toList();
return hospitalList;//return List not object
} catch (error, stacktrace) {
print("Exception occurred: $error stackTrace: $stacktrace");
return Future.error("");
}
}
}
hospitalList = mData.map<HospitalListModel>((e) =>hospitalListModelFromJson(e).toList();this code throw an error, and if you wondering how the other class or method, i will put event & state that seems related to the error:
state:
abstract class HospitalListState extends Equatable {
const HospitalListState();
#override
List<Object?> get props => [];
}
class HospitalListInitial extends HospitalListState {}
class HospitalListLoading extends HospitalListState {}
class HospitalListLoaded extends HospitalListState {
final List<HospitalListModel> hospitalListModel;
const HospitalListLoaded(this.hospitalListModel);
}
class HospitalListError extends HospitalListState {
final String? message;
const HospitalListError(this.message);
}
event:
abstract class HospitalListEvent extends Equatable {
const HospitalListEvent();
#override
List<Object> get props => [];
}
class GetCovidList extends HospitalListEvent {}
i made this code with flutter_bloc and if you want to know more details just let me know, and if you know what's wrong with my code, just type it on the answer, i appreciate every answers and knowledge that you share with me
You have missed ')' before using toList method you have close the map method.
hospitalList = mData.map<HospitalListModel>((e) => hospitalListModelFromJson(e))
.toList();

how to pass the model class and return data in flutter

Model class:
#JsonSerializable()
class EmpVerifyEntity extends Equatable {
#JsonKey(name: "access_token")
final String accessToken;
final EmpVerifyEmployee employee;
const EmpVerifyEntity(this.accessToken, this.employee);
factory EmpVerifyEntity.fromJson(Map<String, dynamic> json) =>
_$EmpVerifyEntityFromJson(json);
Map<String, dynamic> toJson() => _$EmpVerifyEntityToJson(this);
static const empty = EmpVerifyEntity(
'123',
EmpVerifyEmployee(
'1', 'avatar', 'sId', 'empName', 'empId', 'designation', 1));
#override
String toString() {
return jsonEncode(this);
}
#override
// TODO: implement props
List<Object?> get props => [accessToken, employee];
}
#JsonSerializable()
class EmpVerifyEmployee extends Equatable {
#JsonKey(name: "password")
final String password;
final String avatar;
#JsonKey(name: "_id")
final String sId;
#JsonKey(name: "emp_name")
final String empName;
#JsonKey(name: "emp_id")
final String empId;
final String designation;
#JsonKey(name: "__v")
final int iV;
const EmpVerifyEmployee(this.password, this.avatar, this.sId, this.empName,
this.empId, this.designation, this.iV);
factory EmpVerifyEmployee.fromJson(Map<String, dynamic> json) =>
_$EmpVerifyEmployeeFromJson(json);
Map<String, dynamic> toJson() => _$EmpVerifyEmployeeToJson(this);
static const empty = const EmpVerifyEmployee(
'password', 'avatar', 'sId', 'empName', 'empId', 'designation', 1);
#override
String toString() {
return jsonEncode(this);
}
#override
// TODO: implement props
List<Object?> get props =>
[password, avatar, sId, empName, empId, designation, iV];
}
auth repo:
class AuthenticationRepository {
Future<void> logIn({ //login process
required String username,
required String password,
}) async {
print("------------------");
print(username);
var res = await http.post(
Uri.parse("https://cots.com/api/v1/employee/login"),
headers: {
'Content-type': 'application/json',
'Accept': 'application/json'
},
body: jsonEncode({"emp_id": username, "password": password}));
dynamic data = json.decode(res.body);
print(data);
if (data['employee']["is_active"] == true) {
_controller.add(AuthenticationStatus.authenticated);///here it shows authenticated
}
}
User Repo:
class UserRepository {
EmpVerifyEntity? _empVerifyEntity;
UserRepository(this._empVerifyEntity);
Future<EmpVerifyEntity?> getUser() async {
if (_empVerifyEntity != null) {
return _empVerifyEntity;
}
}
}
Auth Bloc:
class AuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
AuthenticationBloc({
required AuthenticationRepository authenticationRepository,
required UserRepository userRepository,
}) : _authenticationRepository = authenticationRepository,
_userRepository = userRepository,
super(const AuthenticationState.unknown()) {
on<AuthenticationStatusChanged>(_onAuthenticationStatusChanged);
on<AuthenticationLogoutRequested>(_onAuthenticationLogoutRequested);
_authenticationStatusSubscription = _authenticationRepository.status.listen(
(status) => add(AuthenticationStatusChanged(status)),
);
}
final AuthenticationRepository _authenticationRepository;
final UserRepository _userRepository;
late StreamSubscription<AuthenticationStatus>
_authenticationStatusSubscription;
#override
Future<void> close() {
_authenticationStatusSubscription.cancel();
_authenticationRepository.dispose();
return super.close();
}
void _onAuthenticationStatusChanged(
AuthenticationStatusChanged event,
Emitter<AuthenticationState> emit,
) async {
switch (event.status) {
case AuthenticationStatus.unauthenticated:
return emit(const AuthenticationState.unauthenticated());
case AuthenticationStatus.authenticated:
final user = await _tryGetUser();
return emit(user != null
? AuthenticationState.authenticated(user)
: AuthenticationState.unauthenticated());
default:
return emit(const AuthenticationState.unknown());
}
}
void _onAuthenticationLogoutRequested(
AuthenticationLogoutRequested event,
Emitter<AuthenticationState> emit,
) {
_authenticationRepository.logOut();
}
Future<EmpVerifyEntity?> _tryGetUser() async {
try {
final user = await _userRepository.getUser();
return user; /////----------------Here I'm getting null as a user
} catch (e) {
print(e);
}
}
}
I can able to do login but can't able to fetch data because it shows null, In login method I should return data so that I can able to fetch. It is authenticated but user data is null, how to pass the return data to userRepo so that I can fetch in authBloc.
-------------------------------------THank you--------------------------

How to retrieve a list of maps from firebase flutter

I'm working on my project in flutter and I'm stuck in kind of a dumb problem. I'd like to retrieve a List of maps from Firebase, but i can't convert the document in an object format.
So every checkout document is identified by the user email. As a consequence every document will contain a list of checkout orders as displayed in the picture.
This is the Document format in firebase
class UserCheckoutOrderList extends Equatable {
final String checkoutOrderDate;
final Map<dynamic, dynamic> customerAddress;
final String customerName;
final String customerPhone;
final String deliveryFee;
final String subTotal;
final String total;
final List<Product> products;
const UserCheckoutOrderList(
{required this.checkoutOrderDate,
required this.customerAddress,
required this.customerName,
required this.customerPhone,
required this.deliveryFee,
required this.subTotal,
required this.total,
required this.products});
static UserCheckoutOrderList fromSnapshot(List<DocumentSnapshot> snapshots) {
final ordersList = snapshots.forEach((snapshot) {
return UserCheckoutOrderList(
checkoutOrderDate: snapshot['checkoutOrderDate'],
customerAddress: snapshot['customerAddress'],
customerName: snapshot['customerName'],
customerPhone: snapshot['customerPhone'],
deliveryFee: snapshot['deliveryFee'],
subTotal: snapshot['subTotal'],
total: snapshot['total'],
products: snapshot['products']);
});
return ordersList;
}
#override
List<Object?> get props => [];
}
This is what I've tried so far for the model.
I highly recommend to always have an id on any document. This is how to handle data:
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
#immutable
class UserCheckoutOrderListEntity extends Equatable{
final String id;
final String checkoutOrderDate;
final Map customerAddress;
final String customerName;
final String customerPhone;
final String deliveryFee;
final String subTotal;
final String total;
final List products;
const UserCheckoutOrderListEntity({
required this.id,
required this.checkoutOrderDate,
required this.customerAddress,
required this.customerName,
required this.customerPhone,
required this.deliveryFee,
required this.subTotal,
required this.total,
required this.products});
Map<String, dynamic> toMap() {
return {
'id': id,
'checkoutOrderDate': checkoutOrderDate,
'customerAddress': customerAddress,
'customerName': customerName,
'customerPhone': customerPhone,
'deliveryFee': deliveryFee,
'subTotal': subTotal,
'total': total,
'products': products,
};
}
factory UserCheckoutOrderListEntity.fromMap(Map<String, dynamic> map) {
return UserCheckoutOrderListEntity(
id: map['id'] as String,
checkoutOrderDate: map['checkoutOrderDate'] as String,
customerAddress: map['customerAddress'] as Map,
customerName: map['customerName'] as String,
customerPhone: map['customerPhone'] as String,
deliveryFee: map['deliveryFee'] as String,
subTotal: map['subTotal'] as String,
total: map['total'] as String,
products: map['products'] as List,
);
}
#override
List<Object?> get props => [id];
}
Then in your widget:
class CheckOut extends StatelessWidget {
const CheckOut({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return FutureBuilder<List<UserCheckoutOrderListEntity>>(
future: FirebaseFirestore.instance
.collection("orderCheckOut")
.get()
.then((query) => query.docs
.map((map) => UserCheckoutOrderListEntity.fromMap(map.data()))
.toList()),
builder: (context, snapshot) {
return Container();
});
}
}

How to use polymorphism in dart

Hi I need to use polymorphism in flutter to use it in get data from Restfull API
but it doesn't work.
first I have a base class (Model)
class Model
{
Model();
Model.fromJson(Map<String, dynamic> json) ;
Map<String, dynamic> toJson(){}
}
then I have derived class (City)
class City extends Model {
int id;
String name;
List<Region> regions;
City({this.id, this.name, this.regions});
City.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
if (json['regions'] != null) {
regions = new List<Region>();
json['regions'].forEach((v) {
regions.add(new Region.fromJson(v));
});
}
}
}
and this is a function with getAllModel method
class Repository
{
static Future<List<T>> getAllModel<T extends Model>( apis url) async {
try {
bool tk = await Cache.tokenExists();
if (tk) {
String token = await Cache.getString(keys.api_token);
var body = {};
List<dynamic> data = await Request.callAPI(
apiName.name(url),
body,
method: 'GET', apiToken: token);
List<T> objs = new List();
if (data != null) if (data.length > 0) {
data.forEach((v) {
T r = Model.fromJson(v) as T;
objs.add(r);
});
return objs;
}
return objs;
}
return new List();
} catch (e, stacktrace) {
print("getAll() | catch exception");
print(e);
print(stacktrace);
return new List();
}
}
}
finally this is a call method
static Future<List<City>> getCities() async {
return Repository.getAllModel(apis.getCities);
}
this is a problem
type 'Model' is not a subtype of type 'City' in type cast
How can I solve this ?
Model.fromJson(v) will always return a value of the base type, Model.
You can't cast this to T = City here:
T r = Model.fromJson(v) as T;
because Dart can't know how to turn a Model into a City.
You would want to call “T.fromJson(v)”, so to speak, but Dart doesn't allow us to speak polymorphically about constructors.
I think your best bet might be to pass the relevant fromJson function along to getAllModel:
// The type of functions that take a JSON map and return T.
typedef JsonDecoder<T> = T Function(Map<String, dynamic> json);
// Use a provided `fromJson` callback to transform your API results:
Future<List<T>> getAllModel<T extends Model>(String url,
JsonDecoder<T> fromJson) async {
...
T r = fromJson(v);
...
}
// And provide it like so:
void myFunction() {
final cities = await getAllModel<City>("city url", (j) => City.fromJson(j));
}
(Somewhat annoyingly, we can't even write this:)
getAllModel<City>("city url", City.fromJson);
^^^^^^^^^^^^^
By the way, I recommend looking into json_serializable.
Here is an aproach i am thinking of taking to render different components returned from a REST api (strapi)
I want to render the components based on type from a response like this
{
"data": {
"id": 1,
"homepage": [
{
"id": 1,
"__component": "explore.search",
"enabled": true,
"include_articles": true,
"include_menu_entries": true,
"include_contributors": true
},
{
"id": 1,
"__component": "explore.whats-new",
"entry_count": 7
}
]
},
"meta": {}
}
I created a base component model like this to capture the common parameters to be extended by each component model later
class CmsComponent {
CmsComponent({
required this.id,
required this.component,
});
int id;
String component;
factory CmsComponent.fromJson(Map<String, dynamic> json) => CmsComponent(
id: json["id"],
component: json["__component"],
);
Map<String, dynamic> toJson() => {
"id": id,
"__component": component,
};
}
then an example of a single component model is
class CmsComponentSearch extends CmsComponent{
CmsComponentSearch({
required id,
required component,
required this.enabled,
required this.includeArticles,
required this.includeMenuEntries,
required this.includeContributors,
}) : super(id: id, component: component);
bool enabled;
bool includeArticles;
bool includeMenuEntries;
bool includeContributors;
factory CmsComponentSearch.fromJson(Map<String, dynamic> json) => CmsComponentSearch(
id: json["id"],
component: json["__component"],
enabled: json["enabled"],
includeArticles: json["include_articles"],
includeMenuEntries: json["include_menu_entries"],
includeContributors: json["include_contributors"],
);
Map<String, dynamic> toJson() => {
"id": id,
"__component": component,
"enabled": enabled,
"include_articles": includeArticles,
"include_menu_entries": includeMenuEntries,
"include_contributors": includeContributors,
};
}
then at the top level i can create this model with a method to switch out each components model as needed
class CmsExploreHome {
CmsExploreHome({
required this.id,
required this.components,
});
int id;
List<dynamic> components;
factory CmsExploreHome.fromJson(Map<String, dynamic> json) => CmsExploreHome(
id: json["id"],
components: List<dynamic>.from(json["homepage"].map((x) => getCmsComponentModelFromJson(x))),
);
Map<String, dynamic> toJson() => {
"id": id,
"homepage": List<dynamic>.from(components.map((x) => x.toJson())),
};
}
getCmsComponentModelFromJson(Map<String, dynamic> json) {
switch(json['__component']) {
case 'explore.search' : return CmsComponentSearch.fromJson(json);
case 'explore.whats-new' : return CmsComponentWhatsNew.fromJson(json);
default : return CmsComponent.fromJson(json);
}
}
Thats the data modelling done, then in the UI widget i can do something like this
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('explore'),
for (var component in exploreProvider.cmsExploreHome!.components) _RenderComponent(component),
],
),
and add the following widgets
class _RenderComponent extends StatelessWidget {
const _RenderComponent(this.component, {Key? key}) : super(key: key);
final CmsComponent component;
#override
Widget build(BuildContext context) {
if(component is CmsComponentSearch) return CmsSearchComponent(component as CmsComponentSearch);
return Text("${component.component} not implemented");
}
}
and for example
class CmsSearchComponent extends StatelessWidget {
const CmsSearchComponent(this.component, {Key? key}) : super(key: key);
final CmsComponentSearch component;
#override
Widget build(BuildContext context) {
return Text("Search field");
}
}
That gets the job done, the flutter app can receive dynamic types from the REST api in a list and render them