I am new to Dart and relatively new to coding. I would appreciate some advice on this.
I have 2 api calls and I would like to merge their results into a single list. I am still trying to grasp the concepts of futures, so far I have understood that I can't just add the returned lists. Here's my code:
class ApiClient {
final Client _client;
ApiClient(this._client);
dynamic get(String path) async {
final response = await _client.get(
Uri.parse(
'${ApiConstants.BASE_URL}$path?api_key=${ApiConstants.API_KEY}'),
headers: {
'Content-Type': 'application/json',
},
);
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception(response.reasonPhrase);
}
}
}
class ResultsModel1{
List<Model1>? names;
ResultsModel1({this.names});
ResultsModel1.fromJson(Map<String, dynamic> json) {
if (json['results'] != null) {
names = <Model1>[];
json['results'].forEach((v) {
names!.add(Model1.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (names != null) {
data['results'] = names!.map((v) => v.toJson()).toList();
}
return data;
}
}
class Model1{
final int id;
final int name;
const Model1({required this.id, required this.name});
factory Model1.fromJson(Map<String, dynamic> json){
return Model1(
id: json['id'],
name: json['name'],
);
}
Map<String, dynamic> toJson(){
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['name'] = name;
return data;
}
}
class ResultsModel2{
List<Model2>? titles;
ResultsModel2({this.titles});
ResultsModel2.fromJson(Map<String, dynamic> json) {
if (json['results'] != null) {
titles = <Model2>[];
json['results'].forEach((v) {
titles!.add(Model2.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (titles != null) {
data['results'] = titles!.map((v) => v.toJson()).toList();
}
return data;
}
}
class Model2{
final int id;
final int name;
const Model2({required this.id, required this.title});
factory Model2.fromJson(Map<String, dynamic> json){
return Model2(
id: json['id'],
name: json['name'],
);
}
Map<String, dynamic> toJson(){
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['name'] = name;
return data;
}
}
abstract class API1{
Future<List<Model1>> getModel1();
}
class API1Imp extends API1{
final ApiClient _client;
API1Imp(this._client);
#override
Future<List<Model1>> getModel1() async{
final response = await _client.get('/baseurlextension');
final names = ResultsModel1.fromJson(response).names;
return names ?? [];
}
}
abstract class API2{
Future<List<Model2>> getModel2();
}
class API2Imp extends API2{
final ApiClient _client;
API2Imp(this._client);
#override
Future<List<Model2>> getModel2() async{
final response = await _client.get('/baseurlextension');
final titles = ResultsModel2.fromJson(response).titles;
return titles ?? [];
}
}
I want to finally get a new list let's say ObjectModel[id, title] where model2 is appended below model1
class ObjectImpl {
final API1Imp api1;
final API2Imp api2;
ObjectImpl(this.api1, this.api2);
#override
List<ObjectModel>>> getObject() async {
try {
final names = await API1Imp.getModel1();
final titles = await API2Imp.getModel2();
final objects = names + titles;
return objects;
}
}
}
but I guess it doesn't work like that. Can anyone please help out?
When using the + operator you can't merge two different list types.
When does it like:
final names = await API1Imp.getModel1();
final titles = await API2Imp.getModel2();
final objects = names + titles;
it will give you an error because it's a different type.
instead, you can do it like
final names = await API1Imp.getModel1();
final titles = await API2Imp.getModel2();
final objects = [...names ,...titles];
If you want names and titles to be in the same list just do this:
final objects = [...names, ...titles];
Otherwise, you need to process them individually to add titles to names like so:
final objects = [];
for (int i = 0; i < names.length; i++) {
objects.add('${names[i]} ${titles[i]}');
}
If you want something other than this, then provide some examples so that we understand exactly what you want.
but I guess it doesn't work like that
Instead, it does! It all comes down on your data structure and on what you want to achieve.
You can merge lists with the + operator, but the two list types must match (e.g. you can do [1,2,3] + [4,5,6] obtaining a List<int>, but you can't do add ['a','b','c'] like that).
You can obtain a List<Object> - if that's what you want - using the spread operator like this:
final futureListOne = Future.delayed(Duration(milliseconds: 750), () => [1,2,3]);
final futureListTwo = Future.delayed(Duration(milliseconds: 950), () => ['a','b','c']);
final listOne = await futureListOne;
final listTwo = await futureListTwo;
final mergedList = [...listOne, ...listTwo];
print(mergedList); // [1, 2, 3, a, b, c]
But is having a List<Object> desirable in your use case? That depends on what you need. Usually, having loose types around your code makes it less readable / reusable, but it really depends on the context.
Note. We can use Future.wait to await for both the futures in parallel and increase efficiency.
final lists = await Future.wait([futureListOne, futureListTwo]);
final listOne = lists[0];
final listTwo = lists[1];
Hope this helps.
Thanks everyone for the answers. After some struggle it worked like this
final names = await API1Imp.getModel1();
final titles = await API2Imp.getModel2();
final objects = [...names ,...titles];
Related
I'm trying to list information from an API remaining on the screen. I have this method:
late List<MyModel> _listAll; // original list fetched from API
late List<MyModel> _displayList;
.
.
void _ListTransaction() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String token = prefs.getString("userToken") ?? "";
dynamic data = await http.get(Uri.parse('....'), headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': '$token',
});
List<MyModel> listAll= [];
for (var u in data) {
MyModel myModel = MyModel.fromJson(u);
print(myModel);
listAll.add(myModel);
}
setState(() {
_listAll = listAll;
_displayList = _listAll ;
});
}
Here I get the error:
_TypeError (type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Iterable')
I also tried this approach:
void _ListTransaction() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String token = prefs.getString("userToken") ?? "";
dynamic data = await http.get(Uri.parse('....'), headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': '$token',
});
var list = json.decode(data.body);
print(list);
setState(() {
_listAll = list;
_displayedList = _petrolList;
});
}
But here I get the error:
_TypeError (type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'List')
You guys could help me with this error. I appreciate any comments!
UPDATE
I used this approach too, but to no avail!
Here is the structure of my json.decode(data.body);
MyModel:
class TransactionModel {
int? month;
int? year;
double? balanceFull;
double? balanceMonth;
ExpenseModel? expenseModel;
RevenueModel? revenueModel;
TransactionModel();
Transactions() {
month = 00;
year = 0000;
balanceFull = 0;
balanceMonth = 0;
}
TransactionModel.fromJson(Map<String, dynamic> json) {
month = json['month'];
year = json['year'];
balanceFull = json['balanceFull'];
balanceMonth = json['balanceMonth'];
expenseModel = json['expenseModel'];
revenueModel = json['revenueModel'];
}
I think that the error is that _listAll is a type list of the MyModel, but the list in the "setState" method is Map<String, dynamic>.
I hope to help you.
You should do something like this :
var result = json.decode(data.body);
List<Map<String, dynamic>> list = (result as List).map((e) => e as Map<String, dynamic>).toList();
list.forEach((element) {
_listAll.add(MyModel.fromJson(element));
}
In my case this works because i have a list of Map<String, dynamic> in my result var from my API.
EDIT :
Based on your comment and the screenshot, you should do this :
Map<String, dynamic> result = json.decode(data.body) as Map<String, dynamic>;
result.forEach((element) {
_listAll.add(MyModel.fromJson(element));
}
And in TransactionModel :
TransactionModel.fromJson(Map<String, dynamic> json) {
month = json['month'];
year = json['year'];
balanceFull = json['balanceFull'];
balanceMonth = json['balanceMonth'];
List<Map<String, dynamic>> expenseList = (json["expenses"] as List).map((e) => e as Map<String, dynamic>).toList();
List<Map<String, dynamic>> revenueList = (json["revenues"] as List).map((e) => e as Map<String, dynamic>).toList();
expenseModel = ExpenseModel.fromJson(list.first) // You can loop
revenueModel = RevenueModel.fromJson(revenueList.first) // Same
}
The idea is that you need to convert every nested model you have to Map<String, dynamic> before making the conversion.
I had the sameissue not so long ago.
Last week i had a similar problem.
I solved it using the follow code:
int _offset = 0;
List<Skeleton> array_news = [];
Future<List<Skeleton>> fetchNews(int offset) async {
final response = await http.get(Uri.parse(
'http://192.168.15.5:5000//custom-events?limit=3&offset=$offset'));
return parseNews(response.body);
}
List<Skeleton> parseNews(String responseBody) {
final parsed =
json.decode(responseBody)['events'].cast<Map<String, dynamic>>();
return parsed.map<Skeleton>((json) => Skeleton.fromJson(json)).toList();
}
void executeScript(int offset) async {
List news = await fetchNews(offset);
setState(() {
news.forEach((data) {
array_news.add(Skeleton(
id: data.id,
title: data.title,
page_link: data.page_link,
image_link: data.image_link));
});
});
}
Skeleton code:
class Skeleton {
int id = 0;
String title = '';
String page_link = '';
String image_link = '';
Skeleton(
{required this.id,
required this.title,
required this.page_link,
required this.image_link});
Skeleton.fromJson(Map<String, dynamic> json) {
id = json['id'];
title = json['title'];
page_link = json['page_link'];
image_link = json['image_link'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['title'] = this.title;
data['page_link'] = this.page_link;
data['image_link'] = this.image_link;
return data;
}
}
After that just access array_news list data through index.
For example:
array_news[0].title;
so i have this http req payload, and i want to push it into an array, can someone help me?
The payload
{
"status":200,
"length":3,
"results":[
{
"_id":"60cd70b3fb9fe400117e8c6b",
"title":"Welcome to xxx",
"body":"Welcome to xx! We’re excited that everyone’s here and hope your ready for an epic weekend."
},
{
"_id":"60cd70b3fb9fe400117e8c6c",
"title":"Lunch Info",
"body":"Lunch is from our generous sponsors Lorem Ipsum! It will be served in the left atrium under the palm trees."
},
{
"_id":"60cd70b3fb9fe400117e8c6d",
"title":"Leash Dogs",
"body":"A friendly reminder that dogs must be leashed at all times, no matter how cute <3"
}
]
}
My Provider Code [UPDATED]
//So I've tried to debug on my own, and number 1 and number 2 is printed, while number 3 is not. I suspect its because of the way I handle extractedData.
class Announcements {
int? status;
int? length;
List<Results>? results;
Announcements(
{required this.status, required this.length, required this.results});
Announcements.fromJson(Map<String, dynamic> json) {
status = json['status'];
length = json['length'];
if (json['results'] != null) {
results = [];
json['results'].forEach((v) {
results!.add(new Results.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['status'] = this.status;
data['length'] = this.length;
if (this.results != null) {
data['results'] = this.results!.map((v) => v.toJson()).toList();
}
return data;
}
}
// so i've used your online converter json
class Results {
String? sId;
String? title;
String? body;
Results({required this.sId, required this.title, required this.body});
Results.fromJson(Map<String, dynamic> json) {
sId = json['_id'];
title = json['title'];
body = json['body'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['_id'] = this.sId;
data['title'] = this.title;
data['body'] = this.body;
return data;
}
}
class AnnouncementProvider with ChangeNotifier {
AnnouncementProvider(String? token, items);
List _items = [];
List get items {
return [..._items];
}
// List<Announcements> parseAnnouncement(String responseBody) {
// }
Future<List<Announcements>> fetchAnnouncements(String authToken) async {
//var url = Uri.https('api-staging.xxx.us.org', '/1.0/announcements');
final response = await http.get(
Uri.parse('https://api-staging.xxx.us.org/1.0/announcements'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $authToken',
},
);
print(response.body);
final t = Announcements.fromJson(response.body as Map<String, dynamic>);
print(t.results);
return t.results;
}
}
What I need to know is, how do I return the list correctly, since the print(t.results) is actually not printed for some reason, so now it only shows "An error has occured" in my interface.
Thanks for helping!
Consider making a Dart Model object for the same, I would highly recommend you to do so because this is guaranteed serialization and type safe
For your case I used an imaginary name FoodItems for the type of data you received from your api endpoint
class FoodItems {
int status;
int length;
List<Results> results;
FoodItems({this.status, this.length, this.results});
FoodItems.fromJson(Map<String, dynamic> json) {
status = json['status'];
length = json['length'];
if (json['results'] != null) {
results = new List<Results>();
json['results'].forEach((v) {
results.add(new Results.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['status'] = this.status;
data['length'] = this.length;
if (this.results != null) {
data['results'] = this.results.map((v) => v.toJson()).toList();
}
return data;
}
}
class Results {
String sId;
String title;
String body;
Results({this.sId, this.title, this.body});
Results.fromJson(Map<String, dynamic> json) {
sId = json['_id'];
title = json['title'];
body = json['body'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['_id'] = this.sId;
data['title'] = this.title;
data['body'] = this.body;
return data;
}
}
Now you can easily cast your response.body to the FoodItems class using fromJson method and then get the desired list of Results and then iterate over it
In my honest opinion, it makes it much simpler this way
Note: I would highly recommend reading the following
This is a nice article from the Flutter Developers themselves
Android Studio Plugin to do the serialization for you
Online converter
My PokemonModel and Results class, i wan't return a List
class PokemonModel {
int count;
String next;
String previous;
List<Results> results;
PokemonModel({this.count, this.next, this.previous, this.results});
PokemonModel.fromJson(Map<String, dynamic> json) {
count = json['count'];
next = json['next'];
previous = json['previous'];
if (json['results'] != null) {
results = [];
json['results'].forEach((v) {
results.add(new Results.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['count'] = this.count;
data['next'] = this.next;
data['previous'] = this.previous;
if (this.results != null) {
data['results'] = this.results.map((v) => v.toJson()).toList();
}
return data;
}
}
class Results {
String name;
String url;
Results({this.name, this.url});
Results.fromJson(Map<String, dynamic> json) {
name = json['name'];
url = json['url'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['name'] = this.name;
data['url'] = this.url;
return data;
}
}
I try use this on repository, i'll need ['next'], ['previous'] and results data to use in widgets but i cannot convert the data to a list of PokemonModel.
That's my current repository where i try get data.
class PokemonRepository implements IPokemonRepository {
Dio _dio;
final String url = 'https://pokeapi.co/api/v2/pokemon/';
PokemonRepository([Dio dio]) : _dio = dio ?? Dio();
#override
Future<List<PokemonModel>> getPokemons() async {
final response = await _dio.get(url);
final poke = PokemonModel.fromJson(response.data);
//how parse and return a list of pokemonmodel?
}
}
There are a couple of ways you can do it.
// method 1 (declarative/functional programming)
final List<PokemonModel> myList = response
.map<PokemonModel>((item) => PokemonModel.fromJson(item))
.toList();
return myList;
or
// method 2 (imperative)
final myList2 = <PokemonModel>[];
for (final Map<String, dynamic> item in response) {
myList2.add(PokemonModel.fromJson(item));
}
return myList2;
I've seen it done both ways. Both return the same result.
i am trying to add the fields of a subcollection into reviewsList. May i know how should I do that in the //To add to List// part?
The 'Reviews' collection contains 2 subcollections namely '1' and '2'. Both '1' and '2' each contain a map of 4 fields.
Below are the codes and screenshot of firestore:
List<dynamic> reviewsList = [];
Future _getReviews() async{
firestore.collection('shops').doc(widget.shop.id).collection('reviews').get()
.then((reviews){
reviews.docs.forEach((result) {
firestore.collection('shops').doc(widget.shop.id).collection('reviews').doc(result.id)
.get().then((reviewDocumentSnapshot) {
// To add to List //
});
});
});
}
the issue is related to misunderstanding of async. change your function as
Future _getReviews() async{
var reviews = await firestore.collection('shops').doc(widget.shop.id).collection('reviews').get();
reviews.docs.forEach((result) {
var reviewDocumentSnapshot= await firestore.collection('shops').doc(widget.shop.id).collection('reviews').doc(result.id);
//add this snapshot to list.
reviewsList[your_object.fromJson(reviewDocumentSnapshot)];
});
}
and your model class will be
class your_model {
String name;
String review;
int star;
String uid;
your_model({this.name, this.review, this.star, this.uid});
your_model.fromJson(Map<String, dynamic> json) {
name = json['name'];
review = json['review'];
star = json['star'];
uid = json['uid'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['name'] = this.name;
data['review'] = this.review;
data['star'] = this.star;
data['uid'] = this.uid;
return data;
}
}
I have following Model
product_model.dart
class ProductModel {
String status;
String message;
List<Results> results;
ProductModel({this.status, this.message, this.results});
ProductModel.fromJson(Map<String, dynamic> json) {
status = json['status'];
message = json['message'];
if (json['data'] != null) {
results = new List<Results>();
json['data'].forEach((v) {
results.add(new Results.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['status'] = this.status;
data['message'] = this.message;
if (this.results != null) {
data['data'] = this.results.map((v) => v.toJson()).toList();
}
return data;
}
}
class Results {
String id;
String productCode;
String category;
String title;
String isActive;
Results(
{this.id,
this.productCode,
this.category,
this.title,
this.isActive,
});
Results.fromJson(Map<String, dynamic> json) {
id = json['id'];
productCode = json['product_code'];
category = json['category'];
title = json['title'];
isActive = json['is_active'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['product_code'] = this.productCode;
data['title'] = this.title;
data['category'] = this.category;
data['is_active'] = this.isActive;
return data;
}
}
I have a functionality to save products to favorites. The favorites will be saved as json in a file.
import 'package:example/utils/favstorage.dart';
import 'package:example/models/product_model.dart';
class FavoriteProducts {
FavoritesStorage storage = FavoritesStorage();
List<ProductModel> favorites = [];
Future addFavorite(ProductModel products) async {
favorites.add(products);
await storage.writeFavorites(favorites);
}
}
I want to add product to favorites only if its not there. How can I update the addFavorite method so that if particular id doesnot exist then only proceed adding to favorites.
I am new to flutter. Can anybody help me on this??
You can use and indexWhere to search your list for an item with the same id, like this:
Future addFavorite(ProductModel products) async {
if(favorites.indexWhere((listProduct) => listProduct.id == products.id) == -1){
favorites.add(products);
await storage.writeFavorites(favorites);
}
}
-1 means there was no item, if there was the item it would have returned it from the list.
Understanding your model:
ProductModel has List
Results has id.
How to see if provided ProductModel can be added to favorite:
Take favorites list and look in List.
in each product model liik in List.
for each Results check if there id is same as any Results in List of provided ProductModel to the method.
If every thing is false, add ProductModel to favorite.
Following is the code for your reference:
Future addFavorite(ProductModel products) async {
bool containsId = favorites.any((ProductModel model){
return model.results.any((Results result){
return products.results.any((Results resultInProducts) => resultInProducts.id == result.id);
});
});
if(!containsId){
favorites.add(products);
await storage.writeFavorites(favorites);
}
}
I hope this helps, in case of any doubt please comment. If this answer helps you then please accept and up-vote the answer.