Flutter - creating a search delegate data 'getter' model, from CSV data - json

At the moment, I am building a Flutter app to try and develop my skills, which includes a search delegate function. At the moment, my search delegate works great, when the data is imported from json format, which looks like this:
[
{
name: John,
age: 22,
height: 1.85,
searchTerm: John 22 1.85,
},
{
name: Alice,
age: 24,
height: 1.90,
searchTerm: Alice 24 1.90,
},
{
name: Bruce,
age: 35,
height: 1.76,
searchTerm: Bruce 35 1.76,
}
]
To allow my search delegate to work, I built a getter model that looks like this.
import 'dart:convert';
import 'package:flutter/services.dart';
class People {
final String name;
final double age;
final double height;
final String searchTerm;
People({
required this.name,
required this.age,
required this.height,
required this.searchTerm,
});
static People fromJson(Map<String, dynamic> json) => People(
name: json['name'],
age: json['age'],
height: json['height'],
searchTerm: json['searchTerm'],
);
}
class PeopleGetter {
static Future<List<People>> getPeopleSuggestions(String query) async {
try {
final String response =
await rootBundle.loadString('assets/data/people.json');
final List people = json.decode(response);
return people
.map((json) => People.fromJson(json))
.where((people) {
final searchTermLower = people.searchTerm.toLowerCase();
final queryLower = query.toLowerCase();
return searchTermLower.contains(queryLower);
}).toList();
} catch (e) {
print(e);
}
throw '';
}
}
This actually does work perfectly with my search delegate. However, since storing the data in a CSV file can significantly reduce the overall data size, and therefore the overall app size, I want to replace the json data with CSV data. I've tried modifying my 'getter' function from json data, to use CSV data (using the CSV package, from pub.dev), which you can see here:
import 'package:csv/csv.dart';
import 'package:flutter/services.dart';
class People {
final String name;
final double age;
final double height;
final String searchTerm;
People({
required this.name,
required this.age,
required this.height,
required this.searchTerm,
});
static People fromCSV(Map<String, dynamic> csv) => People(
name: csv['name'],
age: csv['age'],
height: csv['height'],
searchTerm: csv['searchTerm'],
);
}
class PeopleGetter {
static Future<List<People>> getPeopleSuggestions(String query) async {
try {
final String response =
await rootBundle.loadString('assets/data/people.csv');
final List people = CsvToListConverter(response);
return people
.map((csv) => People.fromCSV(csv))
.where((people) {
final searchTermLower = people.searchTerm.toLowerCase();
final queryLower = query.toLowerCase();
return searchTermLower.contains(queryLower);
}).toList();
} catch (e) {
print(e);
}
throw '';
}
}
Unfortunately, when I use this version within the search delegate, I get the error:
'type 'List<dynamic>' is not a subtype of type 'Map<String, dynamic>'.
I feel like I'll just have made a very simple error somewhere but at the moment, I just can't spot it. I would really appreciate any help correcting my code, to allow me to search with CSV data, instead of JSON data. Thanks!

It seems that your data types are not corresponding anymore:
You provide a List<dynamic> to a function that expects a Map<>
people.map((csv) => People.fromCSV(csv))
static People fromCSV(Map<String, dynamic> csv) => People(
name: csv['name'],
age: csv['age'],
height: csv['height'],
searchTerm: csv['searchTerm'],
);
Could you try rewriting your function from CSV to adapt to the new incoming data ? I'm not sure what is the data you get but maybe something like that ?
static People fromCSV(List<dynamic> csv) => People(
name: csv[0] ?? "",
age: csv[1] ?? -1,
height: csv[2] ?? -1,
searchTerm: csv[3] ?? "",
);
EDIT (fixing the few remaining bugs)
Updated CSV package version
Correctly retrieve the double from the csv
Set the delimiter parameter to separate two people in the CSV
class People {
final String name;
final double age;
final double height;
final String searchTerm;
People({
required this.name,
required this.age,
required this.height,
required this.searchTerm,
});
static People fromCSV(List<dynamic> csv) => People(
name: csv[0] ?? '',
age: double.parse(csv[1].toString()),
height: double.parse(csv[2].toString()),
searchTerm: csv[3] ?? '',
);
}
class PeopleGetter {
static Future<List<People>> getPeopleSuggestions(String query) async {
try {
final String response = await rootBundle.loadString('assets/data.csv');
final List<List<dynamic>> people =
CsvToListConverter().convert(response, eol: "\n");
return people.map((csv) => People.fromCSV(csv)).where((people) {
final searchTermLower = people.searchTerm.toLowerCase();
final queryLower = query.toLowerCase();
return searchTermLower.contains(queryLower);
}).toList();
} catch (e) {
print(e);
}
throw '';
}
}

Related

Flutter: Unhandled Exception: type 'Null' is not a subtype of type 'String' when trying to run app

Trying to bulild app but it keeps crashing! When I click on the error it is leading me to two files video.dart and video_controller.dart.
It is pointing me on specific lines but when I click on them there is no specific error explanation of error I get. I tried to look for solution online but I can't understand what is this error telling me! If u know the solution please let me know!
ERROR:
/flutter ( 5006): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: type 'Null' is not a subtype of type 'String'
E/flutter ( 5006): #0 Video.fromSnap
package:tiktok_project/models/video.dart:48
E/flutter ( 5006): #1 VideoController.onInit.<anonymous closure>
package:tiktok_project/controllers/video_controller.dart:19
video.dart :
import 'package:cloud_firestore/cloud_firestore.dart';
class Video {
String username;
String uid;
String id;
List likes;
int commentCount;
int shareCount;
String songName;
String caption;
String videoUrl;
String thumbnail;
String profilePhoto;
Video({
required this.username,
required this.uid,
required this.id,
required this.likes,
required this.commentCount,
required this.shareCount,
required this.songName,
required this.caption,
required this.videoUrl,
required this.profilePhoto,
required this.thumbnail,
});
Map<String, dynamic> toJson() => {
"username": username,
"uid": uid,
"profilePhoto": profilePhoto,
"id": id,
"likes": likes,
"commentCount": commentCount,
"shareCount": shareCount,
"songName": songName,
"caption": caption,
"videoUrl": videoUrl,
"thumbnail": thumbnail,
};
static Video fromSnap(DocumentSnapshot snap) {
var snapshot = snap.data() as Map<String, dynamic>;
return Video(
username: snapshot['username'],
uid: snapshot['uid'],
id: snapshot['id'],
likes: snapshot['likes'],
commentCount: snapshot['commentCount'],
shareCount: snapshot['shareCount'],
songName: snapshot['songName'],
caption: snapshot['caption'],
videoUrl: snapshot['videoUrl'],
profilePhoto: snapshot['profilePhoto'],
thumbnail: snapshot['thumbnail'],
);
}
}
video_controller.dart:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:get/get.dart';
import 'package:tiktok_project/const.dart';
import 'package:tiktok_project/models/video.dart';
class VideoController extends GetxController {
final Rx<List<Video>> _videoList = Rx<List<Video>>([]);
List<Video> get videoList => _videoList.value;
#override
void onInit() {
super.onInit();
_videoList.bindStream(
firestore.collection('videos').snapshots().map((QuerySnapshot query) {
List<Video> retVal = [];
for (var element in query.docs) {
retVal.add(
Video.fromSnap(element),
);
}
return retVal;
}));
}
likeVideo(String id) async {
DocumentSnapshot doc = await firestore.collection('videos').doc(id).get();
var uid = authController.user.uid;
if ((doc.data() as dynamic)['likes'].contains(uid)) {
await firestore.collection('videos').doc(id).update({
'likes': FieldValue.arrayRemove([uid])
});
} else {}
await firestore.collection('videos').doc(id).update({
'likes': FieldValue.arrayUnion([uid])
});
}
}
That's a null safety error.
You need to do something like this
username: snapshot['username'] ?? '',
For each element that is String, or use the type "String?" with the interrogation point telling the compiler that this field may be null
Apparently I can't reproduce the error since I don't have access to your firestore documents, but I am pretty certain that the problem occurs in
...
for (var element in query.docs) {
retVal.add(
Video.fromSnap(element),
);
}
...
where you attempt to translate element JSON into a Video. In fromSnap, before returning, try printing out snapshot. You should see that it is missing one or more attributes (which is why it was complaining about having a Null when it should be a String), or have some incorrect attributes.

returned json list returning null after deserializing flutter

'
Future<List<ItemModel>?> fetchCategories(String category) async {
final response = await http.get(Uri.http(base_url, '$path/$category'));
if (response.statusCode == 200) {
final items = jsonDecode(response.body) as List;
print(items);
List<ItemModel> itemsList =
items.map((e) => ItemModel.fromJSON(e)).toList();
return itemsList;
} else {
throw Exception("Could not fetch courses");
}
}
this code doesn't return anything. Even though i define class for it. and also it gives json response.the problem is i cant change the returned json array into the object. i refer to a lot of stackoverflow questions. but i cant get any different response. in the bottom i put the response for print(items).
[ {category: truck, closing_date: Thu 24 2021,
closing_hour: 23:23, image:
https://images.unsplash.com/photo-1501700493788-fa1a4fc9fe62?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=681&q=80,
increment: 50, item_id: 61237eb46a27a9571ad912d4, item_name: land cruiser, minimum_price: 4500, user: 61260a5e9}]
here is the model
class ItemModel {
final String ?id;
final String ?itemName;
final String ?user;
final int ?increment;
final int ?minPrice;
final String ?closingDate;
final String ?closingTime;
final String ?category;
final String ?image;
ItemModel({
this.id,
this.itemName,
this.user,
this.increment,
this.minPrice,
this.closingDate,
this.closingTime,
this.category,
this.image,
});
factory ItemModel.fromJSON(Map<String,dynamic> jsonMap){
final result = ItemModel(
id:jsonMap['item_id'],
itemName: jsonMap['item_name'],
increment: jsonMap['increment'],
minPrice: jsonMap['minimum_price'],
user:jsonMap['user'],
category: jsonMap['category'],
closingDate: jsonMap['closing_date'],
closingTime: jsonMap['closing_hour'],
image: jsonMap['image']
);
return result;
}
}

How to add an Object with a DocumentReference type in firebase (Flutter/dart)?

I want to reference a category document in my post document in firebase.
This is my data class, I'm also using freezed and json_serializer:
part 'post_dto.freezed.dart';
part 'post_dto.g.dart';
part 'category_dto.freezed.dart';
part 'category_dto.g.dart';
#freezed
abstract class PostDTO with _$PostDTO {
const PostDTO._();
const factory PostDTO({
#JsonKey(ignore: true) String? id,
required String title,
required String description,
#DocumentReferenceConveter() DocumentReference? categoryReference,
}) = _PostDTO;
factory PostDTO.fromJson(Map json) =>
_$PostDTOFromJson(json);
factory PostDTO.fromFireStore(DocumentSnapshot document) {
Map data = document.data() as Map;
return PostDTO.fromJson(data).copyWith(id: document.id);
}
}
#freezed
abstract class CategoryDTO with _$CategoryDTO {
const CategoryDTO._();
const factory CategoryDTO({
required String icon,
required String name,
}) = _CategoryDTO;
factory CategoryDTO.fromFireStore(DocumentSnapshot document) {
Map data = document.data() as Map;
return CategoryDTO.fromJson(data);
}
factory CategoryDTO.fromJson(Map json) =>
_$CategoryDTOFromJson(json);
}
When I run build_runner I got this error:
[SEVERE] json_serializable:json_serializable on lib/infrastructure/post/post_dto.dart:
Could not generate `fromJson` code for `categoryReference`.
To support the type `DocumentReference` you can:
* Use `JsonConverter`
https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonConverter-class.html
* Use `JsonKey` fields `fromJson` and `toJson`
https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonKey/fromJson.html
https://pub.dev/documentation/json_annotation/latest/json_annotation/JsonKey/toJson.html
package:UPLFY/infrastructure/post/post_dto.freezed.dart:373:41
╷
373 │ final DocumentReference? categoryReference;
│ ^^^^^^^^^^^^^^^^^
╵
[INFO] Running build completed, took 2.5s
[INFO] Caching finalized dependency graph...
[INFO] Caching finalized dependency graph completed, took 44ms
[SEVERE] Failed after 2.5s
So tried using the JsonConverter but I'm not sure how to convert the json object to a DocumentReference...
class DocumentReferenceConveter
implements JsonConverter, Object> {
const DocumentReferenceConveter();
#override
DocumentReference fromJson(Object json) {
return //TODO: Convert json to DocumentReference
}
#override
Object toJson(DocumentReference documentReference) =>
documentReference;
}
I was able to put together my solution from the research I found online and so far came up with this.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:json_annotation/json_annotation.dart';
class DocumentReferenceJsonConverter
implements JsonConverter<DocumentReference?, Object?> {
const DocumentReferenceJsonConverter();
#override
DocumentReference? fromJson(Object? json) {
return tryCast<DocumentReference>(json);
}
#override
Object? toJson(DocumentReference? documentReference) => documentReference;
}
T? tryCast<T>(value) {
return value == null ? null : value as T;
}
...
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user_profile.freezed.dart';
part 'user_profile.g.dart';
#freezed
class UserProfile with _$UserProfile {
const UserProfile._();
#TimestampConverter()
#DocumentReferenceJsonConverter()
#JsonSerializable(
explicitToJson: true,
fieldRename: FieldRename.snake,
includeIfNull: false,
)
factory UserProfile({
#JsonKey(ignore: true) DocumentReference? reference,
String? avatarUrl,
required String email,
required String firstName,
required String lastName,
Gender? gender,
DateTime? birthday,
String? additionalInfo,
Contact? contact,
DocumentReference? familyReference,
DateTime? createdAt,
}) = _UserProfile;
factory UserProfile.empty() => UserProfile(email: '', firstName: '', lastName: '');
factory UserProfile.fromJson(Map<String, dynamic> json) => _$UserProfileFromJson(json);
factory UserProfile.fromDocument(DocumentSnapshot documentSnapshot) {
final data = documentSnapshot.data();
return data != null
? UserProfile.fromJson(data as Map<String, dynamic>)
.copyWith(reference: documentSnapshot.reference)
: UserProfile.empty();
}
I have been investigating and I found there is an issue related to some versions of the analyzer package. I leave it here in case it could be useful for someone in the community (if you use the '0.39.15' or '0.39.16' versions this could be the cause). If that is the case, you can set the override for now inside your pubspec.yaml:
dependency_overrides:
analyzer: '0.39.14'
Also, you should clear all of the caches after:
flutter clean
flutter pub cache repair
flutter pub run build_runner clean

Assign value to a variable from list object converted from JSON in Flutter

I'm really new to flutter. I tried searching for this question and although I have found a solution but not all my problems are solved, as most of it just use return.
I have a JSON that i get from API calling here:
{
"error": false,
"message": "LOGIN_SUCCESS",
"user": {
"id": 1219,
"email": "john#example.com"
"name": "John Doe",
"category": 1,
"branch": 1004,
"lastlogin": "2020-12-04 03:12:43"
}
}
I already create the class for user as below
class User {
int id;
String name;
String email;
String category;
String branch;
String lastLogin;
User({
this.id,
this.name,
this.email,
this.category,
this.branch,
this.lastLogin
});
factory User.fromJson(Map<String, dynamic> datauser){
return User(
id: datauser['id'],
name: datauser['name'],
email: datauser['email'],
category: datauser['category'],
branch: datauser['branch'],
lastLogin: datauser['lastlogin']
);
}
}
and a result class as below..
class Result {
String message;
User user;
Result({
this.message,
this.user
});
factory Result.fromJson(Map<String, dynamic> resultData){
return Result(
message: resultData['message'],
user: User.fromJson(resultData['user'])
);
}
}
now here comes my problem as i don't know how to move forward from this point
login() async {
List<User> users;
final response = await http.post("myUrlWithAPIcalls",
body: {"email": email, "password": password});
final data = jsonDecode(response.body);
var rest = data['user'] as List;
users = rest.map<User>((json) => User.fromJson(json)).toList();
}
so the question is, how can i assign the value i get from the JSON that has converted into list into a variable?
now in example if it was only a simple JSON object, i could do it like this..
final data = jsonDecode(response.body);
int id = data['id'];
String name = data['name'];
String email = data['email'];
String category = data['category'];
String branch = data['branch'];
but how can i do that in a list object?
as of anyone was wondering why do i do like this, i was trying to save the data into a sharedpref class that i copied from someone else code.
Shared preferences are not meant to store objects. Use something like sqflite to persist objects (official cookbook here).
I don't understand why your JSON shows one user's data, but the login() function seems to decode a list of users.
I'm guessing that's what you want :
login() async {
final response = await http.post("myUrlWithAPIcalls",
body: {"email": email, "password": password});
final data = jsonDecode(response.body);
var user = User.fromJson(data['user']); // the variable you want
}
You don't say where that login() function is, or what you want to do with that User object. FYI, an essential part of Flutter is state management.

_CastError (type 'Client' is not a subtype of type 'List<dynamic>' in type cast)

My API returns the following response:
[{id: 1, nome: foo}, {id: 2, nome: bar}]
And I created a model Client to represent each one:
class Client {
final int id;
final String name;
Client({
this.id,
this.name,
});
factory Client.fromJson(Map<String, dynamic> json) {
return Client(
id: json['id'],
name: json['nome'],
);
}
Map<String, dynamic> toJson() => {
'id': id,
'nome': name,
};
}
Then, in my repository, the method fetching the data above is as follows:
Future<List<Client>> getClients() async {
try {
final _response = await _dio.get(
'/clientes',
options: Options(
headers: {'Authorization': 'Bearer $TOKEN'},
),
);
return Client.fromJson(_response.data[0]) as List; // Error pointed to this line
} on DioError catch (_e) {
throw _e;
}
}
Being stored here
#observable
List<Client> clients;
I am not sure what to do. What am I doing wrong?
dio will decode the response and you'll get a List<dynamic>. Use List.map to convert it to a list of clients, by passing a function that will turn a Map<String, dynamic> into a Client. (You already have one - the named constructor.)
For example:
var dioResponse = json.decode('[{"id": 1, "nome": "foo"}, {"id": 2, "nome": "bar"}]');
List<dynamic> decoded = dioResponse;
var clients = decoded.map<Client>((e) => Client.fromJson(e)).toList();
You're trying to cast a Client as a List<dynamic> which isn't a valid cast since Client doesn't implement List. If you want to return a List containing a single Client, you'll want to change the line with the error to:
return [Client.fromJson(_response.data[0])];