Related
import 'package:flutter/material.dart';
import 'package:http_request/carditem.dart';
import 'package:http_request/user_data.dart';
// here we chose to have statefull widget because the state needs to be updated and we need to update some variables according to some conditions.
class UserScreen extends StatefulWidget {
const UserScreen({Key key}) : super(key: key);
#override
_UserScreenState createState() => _UserScreenState();
}
class _UserScreenState extends State<UserScreen> {
// here we created a variable to keep our information in it . this information comes from our user data class where we get the user data from http requests.
Map<String, dynamic> userinfo = {};
// this method is called as soon as the main widget is created . we call our http method here to have the live data as soon as we open the app .be aware that we cant use async and await here . we just call the method.
#override
void initState() {
super.initState();
getData();
}
// here we get hte live data from our userdata class .
Future getData() async {
//we create a userData object to get access to the getuserdata method.
UserData userData = UserData();
//we handle any error here
try {
// here we create a variable to the exact same type we are expecting .
//then we wait for the result of calling our userdata information.
Map<String, dynamic> data = await userData.getUserData();
// we call setState because we need to update a certain value here.
setState(() {
//we assign the the value of data to our variable called userinfo .try the reaosn we see no error behind them is that they are the same type.
userinfo = data;
});
// we catch errors here .
} catch (e) {
print(e);
}
}
// our nice build method ...this method is from our stateful class.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Work Harder'),
centerTitle: true,
),
body: ListView.builder(
itemBuilder: (context, index) => CardItem(
firstName: userinfo['firstName'],
avatarLink: userinfo['avatarLink'],
id: userinfo['id'],
lastName: userinfo['lastName'],
email: userinfo['email'],
),
),
);
}
}
as you can see here is my user_screen dart file the problem is when i hot reload the app is see the renderflex error, while everything seems fine. im a begginner in flutter and i would really appreciate your help.
i have tried singlechild scrollview , list view , expanded widget , but it doesn't work . please help me hotrestart may app without these long errors
import 'package:flutter/material.dart';
// we chose stateless because we don't need to change the state of our app.
class CardItem extends StatelessWidget {
const CardItem({
Key key,
this.firstName,
this.email,
this.avatarLink,
this.id,
this.lastName,
}) : super(key: key);
final String firstName;
final String email;
final String avatarLink;
final String id;
final String lastName;
#override
Widget build(BuildContext context) {
return Card(
color: Colors.lightBlueAccent[200],
elevation: 3,
shadowColor: Colors.blue[100],
child: Column(
children: [
Stack(
children: [
ListTile(
leading: Icon(Icons.check_box),
trailing: CircleAvatar(
child: Image.network(avatarLink),
),
title: Text(firstName),
subtitle: Text(lastName),
),
Positioned(
left: 48,
top: 15,
child: Text(
id,
style: TextStyle(
color: Colors.white70,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
Positioned(
left: 120,
top: 30,
child: Text(
'Email : $email',
style: TextStyle(
color: Colors.blue[900].withOpacity(0.6),
),
),
),
],
),
],
),
);
}
}
here is my card widget , if it helps.
import 'dart:convert';
import 'package:http/http.dart' as http;
// this is the file for our main http requests.
class UserData {
UserData({
this.firstName,
this.lastName,
this.email,
this.id,
this.avatarLink,
});
final String firstName;
final String lastName;
final String email;
final String id;
final String avatarLink;
//this method is response for getting the live data we want also its responsible for decoding it with 'dart convert' library and returning the result as an output.
Future getUserData() async {
//the url we are working with
final String stringUrl = 'https://reqres.in/api/users?page=2';
//sending the request to web and storing the result to a variable. we also have to wait for the results . (asynchronous operation).
http.Response response = await http.get(
Uri.parse(stringUrl),
);
//we create a variable to the exact type of our output . we want to add our items to this map to use it in the main screen.
Map<String, dynamic> userdetails = {};
//here we want to check if our request was successful if so, we continue our operation and if not we throw an error .
if (response.statusCode == 200) {
// here we are decoding the data that we got from our request above to a variable of type map.
Map<String, dynamic> decodedData = jsonDecode(response.body);
//here we try to iterate through a map .. but im not sure if its correct or not .
for (var user in decodedData.values) {
Map<String, dynamic> myusersInfo = {
'firstName': decodedData['data'][0]['first_name'],
'lastName': decodedData['data'][0]['last_name'],
'email': decodedData['data'][0]['email'],
'id': decodedData['data'][0]['id'].toString(),
'avatarLink': decodedData['data'][0]['avatar'],
};
// here we are trying to add the items to the map specified.
userdetails.addEntries(myusersInfo.entries);
return userdetails;
}
} else {
print(response.statusCode);
throw 'Problem with the get request';
}
}
}
the whole goal of this app was to practice some http request.
the last file I have in this app . I just added this maybe it helps you . thanks in advance.
A Column likes to expand until infinity.
This can be solved by putting an intrinsicHeight around the Column as followed:
This class is useful, for example, when unlimited height is available and you would like a child that would otherwise attempt to expand infinitely to instead size itself to a more reasonable height.
IntrinsicHeight(
child: Column(
children: [
],
)
);
https://api.flutter.dev/flutter/widgets/IntrinsicHeight-class.html
try column inside SingleChildScrollView
I have UI for show event calendars and I need to show events from my API. but I don't know how to do it. I try to change List on _event but there's no response. And I need to show it on the calendar so my company calendar can show the event.
this my UI Calendar
import 'package:intl/intl.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:urus_flutter/persentation/custom_color.dart';
import 'package:urus_flutter/persentation/custom_text_style.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Calender extends StatefulWidget {
#override
State<StatefulWidget> createState() => CalenderState();
}
class CalenderState extends State<Calender> {
CalendarController _controller;
Map<DateTime, List<dynamic>> _events;
List<dynamic> _selectedEvents;
DateTime _selectedDate;
SharedPreferences prefs;
#override
void initState(){
super.initState();
_controller = CalendarController();
_events = {
DateTime(2021, 6, 22) : ['Meeting URUS', 'Testing Danai Mobile', 'Weekly Report', 'Weekly Meeting'],
DateTime(2021, 6, 25) : ['Weekly Testing'],
DateTime(2021, 6, 4) : ['Weekly Testing'],
DateTime(2021, 6, 11) : ['Weekly Testing'],
DateTime(2021, 6, 18) : ['Weekly Testing'],
};
}
Map<String, dynamic> encodeMap(Map<DateTime, dynamic> map) {
Map<String, dynamic> newMap = {};
map.forEach((key, value) {
newMap[key.toString()] = map[key];
});
return newMap;
}
Map<DateTime, dynamic> decodeMap(Map<String, dynamic> map) {
Map<DateTime, dynamic> newMap = {};
map.forEach((key, value) {
newMap[DateTime.parse(key)] = map[key];
});
return newMap;
}
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
ListView(
padding: EdgeInsets.only(left: 16, right: 16, top: 52, bottom: 126),
children: [
Text("Kalender Kegiatan",
style: CustomTextStlye.proxima_bold_18_black,),
Container(
child: Padding(
padding: const EdgeInsets.only(top: 28.0),
child: Text("Kalender Anda",
style: CustomTextStlye.proxima_bold_16_black,),
),
),
SizedBox(
height: 20,
),
Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5.0),
boxShadow: [
BoxShadow(
offset: Offset(0, -1),
color: CustomColor.border_grey,
blurRadius: 3.0,
spreadRadius: 1.0)
]
),
child: TableCalendar(
initialCalendarFormat: CalendarFormat.month,
calendarStyle: CalendarStyle(
todayColor: Color(0x9429AAE1),
todayStyle: CustomTextStlye.proxima_bold_12_white,
selectedColor: Color(0xFF29AAE1),
selectedStyle: CustomTextStlye.proxima_bold_12_white,
weekdayStyle: CustomTextStlye.proxima_bold_12_black,
weekendStyle: CustomTextStlye.proxima_bold_12_red,
unavailableStyle: CustomTextStlye.proxima_bold_12,
holidayStyle: CustomTextStlye.proxima_bold_12_red,
markersColor: Color(0xFFA2CD3A),
),
headerStyle: HeaderStyle(
centerHeaderTitle: true,
formatButtonVisible: false,
titleTextStyle: CustomTextStlye.proxima_bold_14_black,
),
availableCalendarFormats: const {CalendarFormat.month: '',},
startingDayOfWeek: StartingDayOfWeek.monday,
calendarController: _controller,
events: _events,
onDaySelected: (date, events,holidays) {
setState(() {
_selectedEvents = events;
_selectedDate = date;
});
},
),
)
],
),
),
Container(
child:Padding(
padding: const EdgeInsets.only(top: 28.0),
child: Text("Kegiatan Anda",
style: CustomTextStlye.proxima_bold_16_black,),
),
),
Container(
child: _selectedEvents != null ? Column(
children: List.generate(_selectedEvents.length, (index) =>
Container(
padding: const EdgeInsets.all(8.0),
child: Container(
height: MediaQuery.of(context).size.height/15,
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Color.fromRGBO(228, 228, 228, 1)))
),
child:
Center(
child:
Container(child:
Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 10.0),
height: MediaQuery.of(context).size.height/10,
decoration: BoxDecoration(
border: Border.all(color:Color(0xFF29AAE1)),
color:Color(0xFF29AAE1),
borderRadius: BorderRadius.circular(3.0),
),
child: Text(DateFormat('d').format(_selectedDate),
style: CustomTextStlye.proxima_bold_18_white,
),
),
),
Text(_selectedEvents[index],
style: CustomTextStlye.proxima_bold_14_black,
),
],
),
)
),
),
),
),
) : Container(),
)
],
),
],
),
);
}
}
Here my eventCalendarService.dart
import 'dart:convert';
import 'dart:io';
import 'package:http/io_client.dart';
import 'package:urus_flutter/data/eventController.dart';
import 'package:urus_flutter/data/model/base/event_calendar.dart';
Future<List<Event_Calendar>> fetchEventCalendar(String id, int company_id, {String date}) async {
String requestBody = '';
print(requestBody);
final ioc = new HttpClient();
ioc.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
final http = new IOClient(ioc);
final response = await http.get(
getStringUrl+'staffs/GetCalendar?companyid=$company_id&month=$date',
);
print(response.statusCode);
print(response.body);
if (response.statusCode == 200) {
var parsed = jsonDecode(response.body);
return List<Event_Calendar>.from(parsed.map((model) => Event_Calendar.fromJson(model)));
} else {
throw Exception(response.body);
}
}
And here my model event_calendar.dart
class Event_Calendar {
final String id;
final String type;
final String date;
final String event_name;
final int company_id;
Event_Calendar(
{
this.id,
this.type,
this.date,
this.event_name,
this.company_id,
}
);
factory Event_Calendar.fromJson(Map<String, dynamic> json) {
return Event_Calendar(
id: json['id'] as String,
type: json['type'] as String,
date: json['date'] as String,
event_name: json['event_name'] as String,
company_id: json['company_id'] as int,
);
}
}
I hope anyone can give me an answer on how to show _events to my API. Thank you. And this is the example value from my API
Well folks now I'm going to show you the way I found to show events and cards coming from an api using the table calendar.
I don't even need to say that this is the way I found things, feel free to add new things and give tips here. so let's go.
first of all we will show the calendar events, but in this step only the markers, your data coming from the api must contain dates if you are here, in my case the dates come as a String, so let's create the model for them
import 'dart:convert';
class EventsModel {
final String dataDoJob;
EventsModel({
required this.dataDoJob,
});
Map<String, dynamic> toMap() {
return {
'data_acao': dataDoJob,
};
}
factory EventsModel.fromMap(Map<String, dynamic> map) {
return EventsModel(
dataDoJob: map['data_acao'],
);
}
String toJson() => json.encode(toMap());
factory EventsModel.fromJson(String source) => EventsModel.fromMap(json.decode(source));
}
this is my model, as you can see I'm just getting the dates. Now let's do the get method to retrieve this data from the api, I'm using getConnect but you can use the http client you want.
#override
Future<List<EventsModel>> getEvents() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
final int? id = sharedPreferences.getInt("idInfluencer");
final String token = sharedPreferences.getString("token") ?? "";
final Response result = await _restClient.get<List<EventsModel>>(
"/job_acoes?influenciador_id=${id.toString()}",
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $token'
}, decoder: (data) {
if (data != null) {
return data
.map<EventsModel>((event) => EventsModel.fromMap(event))
.toList();
}
return <EventsModel>[];
});
if (result.hasError) {
print(result.statusCode);
throw "Erro ao buscar dados";
}
print(result.body);
print(result.statusCode);
return result.body;
}
Well done that we already have a list of dates in hand, but in my case they are strings so I'll have to convert them, like this:
final events = await _jobsServices.getEvents();
//final dateFormat = DateFormat("yyyy-MM-dd");
final eventsConvert =
events.map((date) => (DateTime.parse(date.dataDoJob))).toList();
eventsList.assignAll(eventsConvert);
print("Lista de eventos : $eventsList");
streamController.add(events);
on the first line I'm saving the list in a variable called events and right below I'm converting the strings into date time using the map method, And adding them to an empty list which I created then step by step and this: Create an empty list and add the converted data to it as I did above, my empty list is called eventsList
done that we will show this list in the table calendar
class CalendarWidget extends GetView<HomeController> {
const CalendarWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder<List<EventsModel>>(
stream: controller.streamController.stream,
builder: (context, snapshot) {
return Obx(() {
return TableCalendar(
eventLoader: (day) => controller.eventsList.where((event) => isSameDay(event,day)).toList(), //THIS IS IMPORTANT
focusedDay: controller.focusedDay.value,
firstDay: DateTime(2019),
lastDay: DateTime(2050),
headerStyle:
const HeaderStyle(formatButtonVisible: false), //WEEK VISIBLE
locale: 'pt_BR',
daysOfWeekVisible: true,
calendarFormat: controller.format.value,
onFormatChanged: (CalendarFormat _format) =>
controller.calendarFormat(_format),
onDaySelected: (DateTime userSelectedDay, DateTime focusedDay) =>
controller.selectedDay(userSelectedDay, focusedDay),
calendarStyle: CalendarStyle(
selectedTextStyle: const TextStyle(color: Colors.white),
isTodayHighlighted: true,
selectedDecoration: BoxDecoration(
color: context.buttomThemeClicled,
shape: BoxShape.circle)),
selectedDayPredicate: (DateTime date) {
return isSameDay(controller.focusedDay.value, date);
},
);
});
});
}
}
remembering that i'm using a stateless widget so i need a state manager, and i use getx so it has an obx involving the whole calendar.
in the event loader some people have doubts, you can pass a list or a map on it, in my case I'm working with a list for simplicity's sake, notice that in the event loader I'm doing a simple filtering, comparing the dates of the my calendar with the dates I get from the api, simple isn't it? by doing this your api-based bookmarks will already be shown. Ah, a detail, the stream builder serves to redo my widget whenever there are changes in the api, if you don't know how to work with it, this video will explain: https://www.youtube.com/watch?v=BBelgajHgzY
now let's go to the display part of your events based on days, my events will be displayed on cards like this:
so I built it on a separate page from my home, this part is important because your code will be easier and cleaner, with the widget done, we'll show them on screen like this:
child: Obx(() {
return ListView(
scrollDirection: Axis.vertical,
children: controller.cards
.map(
(c) => AgendaCards(
bottomPosition: 80,
leftPositioned: 260,
maxRadius: 5,
rightPositioned: 5,
secondMaxradius: 5,
topPositioned: 20,
model: c,
),
)
.toList(),
);
}));
the widget called calendar cards is nothing more than the card in the photo above, on it I asked for a model called
final JobsDescriptionCardsModel model;
and called him in the constructor
AgendaCards({
required this.leftPositioned,
required this.rightPositioned,
required this.topPositioned,
required this.bottomPosition,
required this.maxRadius,
required this.secondMaxradius,
required this.model, //HERE
Key? key,
}) : super(key: key);
so let's create this model
class JobsDescriptionCardsModel {
final String descricaoJob;
final String dataDoJob;
final String horarioDoJob;
final int jobId;
final String nome;
JobsDescriptionCardsModel({
required this.descricaoJob,
required this.dataDoJob,
required this.horarioDoJob,
required this.jobId,
required this.nome,
});
Map<String, dynamic> toMap() {
return {
'descricaoJob': descricaoJob,
'dataDoJob': dataDoJob,
'horarioDoJob': horarioDoJob,
'jobId': jobId,
'nome': nome,
};
}
factory JobsDescriptionCardsModel.fromMap(Map<String, dynamic> map) {
return JobsDescriptionCardsModel(
descricaoJob: map['descricao'] ?? "",
dataDoJob: map['data_acao'] ?? "",
horarioDoJob: map['hora_inicial_acao'],
jobId: map['job_acao_id'] ?? 0,
nome: map['job'] ["cliente"] ["nome"] ?? "",
);
}
String toJson() => json.encode(toMap());
factory JobsDescriptionCardsModel.fromJson(String source) => JobsDescriptionCardsModel.fromMap(json.decode(source));
}
and get it on the api
#override
Future<List<JobsDescriptionCardsModel>> getJobsDescrition() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
final int? id = sharedPreferences.getInt("idInfluencer");
final String token = sharedPreferences.getString("token") ?? "";
final result = await _restClient.get<List<JobsDescriptionCardsModel>>(
"/job_acoes?influenciador_id=${id.toString()}",
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $token'
}, decoder: (data) {
if (data != null) {
return data
.map<JobsDescriptionCardsModel>(
(j) => JobsDescriptionCardsModel.fromMap(j))
.toList();
}
return <JobsDescriptionCardsModel>[];
});
if (result.hasError) {
throw ("erro ao buscar dados");
}
return result.body ?? <JobsDescriptionCardsModel>[];
}
the api gives me a list that is important. With the list in hand, we're going to get into the concepts of the table calendar. To proceed and understand what will be done, I recommend that you watch this video: https://www.youtube.com/watch?v=HKuzPQUV21Y&t=291s
having done the configuration of your calendar, I believe you have noticed that when you click on a date and print the variable that has the date data, you will notice that the table calendar gives you a date time as a return, and if you have rendered it the attention in my model i also have a date coming from the api, knowing that we only need to filter the list coming from the api based on the table calendar data like this:
first create an empty list like we did before and a list that will be filtered:
//LISTA DE JOBS CARDS
final cards = <JobsDescriptionCardsModel>[].obs;
//LISTA FILTRADA
var cardsFiltered = <JobsDescriptionCardsModel>[];
the empty list will be filled with the api data like this:
final jobsCards = await _jobsServices.getJobsDescrition();
cards.assignAll(jobsCards);
and with the populated list in hand we will filter this list based on the api dates, like this:
cardsFiltered = jobsCards;
var novaLista = cardsFiltered.where((model) {
return model.dataDoJob
.toString()
.contains(focusedDay.value.toString().substring(1, 10));
});
see that first I assign my populated list to a new list, then I filtered this list based on my model only in the part that contains the date that I stringed, comparing with my variable that has the date when I click on it remembers ? also converted to string, as the table calendar gives me the date along with other numbers that I believe are time information I got the data only from index 1 to 10 which will give me exactly the date contained in the variable. With that done, the table calendar has a property called onDaySelected, and it will display our filtered list like this:
selectedDay(DateTime selectedDayInfo, DateTime focusDayInfo) {
userSelectedDay.value = selectedDayInfo;
focusedDay.value = focusDayInfo;
print(userSelectedDay.value);
print(focusedDay.value);
print("Lista de eventos 2 ${eventsList}");
var novaLista = cardsFiltered.where((model) {
return model.dataDoJob
.toString()
.contains(focusedDay.value.toString().substring(0, 10));
});
cards.assignAll(novaLista);
I created this separate function in a controller and called it in table calendar like this:
onDaySelected: (DateTime userSelectedDay, DateTime focusedDay) =>
controller.selectedDay(userSelectedDay, focusedDay),
done that your calendar will already be displaying the default markers and your cards based on the widget you built, remember to pass the data to your cards using the model you requested via the constructor because it contains the api data. I hope I have help and I'm sorry for the google translator english
_event was not used in the newer version of table_calendar. you can use eventLoader it will display your events on the calendar.
Example:
calendar widget as follows,
TableCalendar<Event>(
firstDay: kFirstDay,
lastDay: kLastDay,
focusedDay: _focusedDay,
selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
eventLoader: _getEventsForDay, // this will load events on calendar
),
_getEventsForDay method as follows,
List<Event> _getEventsForDay(DateTime day) {
return kEvents[day] ?? [];
}
kEvents (event list which you need to mark on the calendar) is as follows, you don't need to create this one if you have a list of events.
final kEvents = LinkedHashMap<DateTime, List<Event>>(
equals: isSameDay,
hashCode: getHashCode,
)..addAll(_kEventSource);
final _kEventSource = Map.fromIterable(List.generate(50, (index) => index),
key: (item) => DateTime.utc(kFirstDay.year, kFirstDay.month, item * 5),
value: (item) => List.generate(
item % 4 + 1, (index) => Event('Event $item | ${index + 1}')))
..addAll({
kToday: [
Event('Event 1'),
Event('Event 2'),
],
});
Event class as follows, (as well you can make it as you prefer)
class Event {
final String title;
const Event(this.title);
#override
String toString() => title;
}
Hope you got it!
I have the following JSON hosted on localhost and am trying to resolve it in a FutureBuilder widget. I cannot resolve the data from the Json to display using the FutureBuilder widget. All I get is "Instance of LiteratureDataModel". Moreover, I cannot get the length of the data snapshot. The error I get is "The getter length isn't defined for the type Object"
{
"data": [
{
"authors": "author_name1",
"edition": "publication_edition1",
"id": 1,
"pubdate": "publication_date1",
"publisher": "publisher1",
"source": "literature_source1",
"title": "literature_title1"
},
{
"authors": "author_name2",
"edition": "publication_edition2",
"id": 2,
"pubdate": "publication_date2",
"publisher": "publisher2",
"source": "literature_source2",
"title": "literature_title2"
}
]
}
I generated the model class for the above Json using the following link:
https://app.quicktype.io/
Here is the class that was generated. I modified it to use null safety. The list of map objects according to the JSON prints successfully in the LiteratureDataModel factory constructor. So, the data is successfully received
class LiteratureDataModel {
LiteratureDataModel({
this.ultLiteratureDataModel,
});
final List<LiteratureModel>? ultLiteratureDataModel;
factory LiteratureDataModel.fromJson(Map<String, dynamic> json) => LiteratureDataModel(
ultLiteratureDataModel: List<LiteratureModel>.from(json["data"].map((data) {
print(data);
return LiteratureModel.fromJson(data);
})).toList(),
);
Map<String, dynamic> toJson() => {
"data": List<dynamic>.from(ultLiteratureDataModel!.map((data) => data.toJson())),
};
}
class LiteratureModel {
LiteratureModel({
this.authors,
this.edition,
this.id,
this.pubdate,
this.publisher,
this.source,
this.title,
});
final String? authors;
final String? edition;
final int? id;
final String? pubdate;
final String? publisher;
final String? source;
final String? title;
factory LiteratureModel.fromJson(Map<String, dynamic> json) => LiteratureModel(
authors: json["authors"],
edition: json["edition"],
id: json["id"],
pubdate: json["pubdate"],
publisher: json["publisher"],
source: json["source"],
title: json["title"],
);
Map<String, dynamic> toJson() => {
"authors": authors,
"edition": edition,
"id": id,
"pubdate": pubdate,
"publisher": publisher,
"source": source,
"title": title,
};
}
Here is the widget for displaying the data:
class Literature extends StatelessWidget {
final LiteratureService _literatureService = LiteratureService();
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(24.0),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(12)),
),
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: FutureBuilder(
future: _literatureService.fetchLiterature(http.Client()),
builder: (context, snapshot){
if (snapshot.hasData)
//return Text("${snapshot.data!}");
return ListView.builder(
//itemCount: snapshot.data!.length,
itemBuilder: (context, index){
return Text("${snapshot.data!}");
});
else
return CircularProgressIndicator();
},
),
)
),
),
);
}
}
The json is hosted on localhost, from which I can successfully pull the json. Here is the class used for fetching the json from local host
class LiteratureService {
LiteratureDataModel literatureFromJson(String literature) {
//print(literature);
return LiteratureDataModel.fromJson(json.decode(literature));
}
String literatureToJson(LiteratureDataModel literatureDataModel) => json.encode(literatureDataModel.toJson());
Future fetchLiterature(http.Client client) async {
final response = await client.get(Uri.parse("http://localhost_ip_address:8000/literature/ultliterature.json"));
//print("Http response: ${response.body}");
return literatureFromJson(response.body);
}
}
I referred to the following link to solve the problem, but could not solve it:
*SOLVED* Use json data stored in class in future builder
In the above link, the OP faced a similar issue as I did. I followed the changes recommended in the correct answer but realized its just another way of creating factory constructor that is there in LiteratureDataModel class. Moreover, I still could not get the length of the data snapshot.
I referred to the below link as well, which shows different ways of JSON serialization and deserialization as well, and still could not solve the problem.
https://bezkoder.com/dart-flutter-parse-json-string-array-to-object-list/#DartFlutter_parse_JSON_string_into_Nested_Object
What am I doing wrong in my code? Any advice on needed corrections to my code will be helpful.
I solved the problem by omitting the LiteratureDataModel class and changing the fetchLiterature() function in the following way:
Future<List<LiteratureModel>> fetchLiterature(http.Client client) async {
final response =
await client.get(Uri.parse("http://localhost_ip_address:8000/literature/literature.json"));
List<LiteratureModel> literatureModel = List<LiteratureModel>.from(
json.decode(response.body)["data"].map((data) => LiteratureModel.fromJson(data)).toList());
return literatureModel;
}
I also made the following changes to FutureBuilder using this link as a guide
builder: (context, AsyncSnapshot<List<LiteratureModel>> snapshot)
If anyone knows a better way of solving the question I posted, please let me know
I make a call to Youtube API and get this Json:
"kind": "youtube#videoListResponse",
"etag": "\"XI7nbFXulYBIpL0ayR_gDh3eu1k/s7-xmHXpuqQxYzDp_wxhm59K4LE\"",
"pageInfo": {
"totalResults": 1,
"resultsPerPage": 1
},
"items": [
{
"kind": "youtube#video",
"etag": "\"XI7nbFXulYBIpL0ayR_gDh3eu1k/pajQ7iBy-7A0V_owifxkw-Kbw-Y\"",
"id": "7lCDEYXw3mM",
"snippet": {
"publishedAt": "2012-06-20T23:12:38.000Z",
"channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
"title": "Google I/O 101: Q&A On Using Google APIs",
"description": "Antonio Fuentes speaks to us and takes questions on working with Google APIs and OAuth 2.0.",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/default.jpg",
"width": 120,
"height": 90
},
"medium": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/mqdefault.jpg",
"width": 320,
"height": 180
},
"high": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/hqdefault.jpg",
"width": 480,
"height": 360
}
But now I want to parse it and get just 3 nodes:
Title
Description
The url for the default thumbnail
Indeed I get the Json response and can see it on the logs, but when try to parse it every time it fails.
This is my code:
final response = await http.get(
'https://www.googleapis.com/youtube/v3/videos?id=HEREGOESMYAPIKEY&part=snippet&id=T0Jqdjbed40');
final parsed = json.decode(response.body).cast<Map<String, dynamic>>();
String title = parsed['items']['snippet']['title'];
print(title);
String description = parsed['items']['snippet']['description'];
print(description);
String thumbnail = parsed['items']['snippet']['thumbnails']['default']['url'];
print(thumbnail);
What you are trying won't work with Dart, this is not javascript. Dart has very strong type system, which is great. You are trying to assign value to a variable you've defined as String, but the response you have defined a dynamic, so Dart can't validate the value assignment. Also items is array, there is no such key items->snippet.
The correct way to do this is to create model deinitions, which will handle deserialisation and also will provide convenient way of accessing properties you are interested in.
class YoutubeResponse {
String kind;
String etag;
String nextPageToken;
String regionCode;
List<Item> items;
YoutubeResponse(
{this.kind,
this.etag,
this.nextPageToken,
this.regionCode,
this.items});
Map<String, dynamic> toJson() => {
'kind': kind,
'etag': etag,
'nextPageToken': nextPageToken,
'regionCode': regionCode,
'items': items,
};
factory YoutubeResponse.fromJSON(Map<String, dynamic> YoutubeResponseJson) {
var list = YoutubeResponseJson['items'] as List;
List<Item> itemsList = list.map((i) => Item.fromJSON(i)).toList();
return new YoutubeResponse(
kind: YoutubeResponseJson['kind'],
etag: YoutubeResponseJson['etag'],
nextPageToken: YoutubeResponseJson['nextPageToken'],
regionCode: YoutubeResponseJson['regionCode'],
mPageInfo: pageInfo.fromJSON(YoutubeResponseJson['pageInfo']),
items: itemsList);
}
}
class Item {
String kind;
String etag;
Id id;
Snippet snippet;
Item({this.kind, this.etag, this.id, this.snippet});
Map<String, dynamic> toJson() => {
'kind': kind,
'etag': etag,
'id': id,
'snippet': snippet,
};
factory Item.fromJSON(Map<String, dynamic> ItemJson) {
return Item(
kind: ItemJson['kind'],
etag: ItemJson['etag'],
id: Id.fromJSON(ItemJson['id']),
snippet: Snippet.fromJSON(ItemJson['snippet']),
);
}
}
class Snippet {
String publishedAt;
String channelId;
String title;
String description;
Thumbnails thumbnails;
String channelTitle;
String liveBroadcastContent;
Snippet(
{this.publishedAt,
this.channelId,
this.title,
this.description,
this.thumbnails,
this.channelTitle,
this.liveBroadcastContent});
Map<String, dynamic> toJson() => {
'publishedAt': publishedAt,
'channelId': channelId,
'title': title,
'description': description,
'thumbnails': thumbnails,
'channelTitle': channelTitle,
'liveBroadcastContent': liveBroadcastContent,
};
factory Snippet.fromJSON(Map<String, dynamic> SnippetJson) {
return Snippet(
publishedAt: SnippetJson['publishedAt'],
channelId: SnippetJson['channelId'],
title: SnippetJson['title'],
description: SnippetJson['description'],
thumbnails: Thumbnails.fromJSON(SnippetJson['thumbnails']) ,
channelTitle: SnippetJson['channelTitle'],
liveBroadcastContent: SnippetJson['liveBroadcastContent'],
);
}
}
class Medium {
int height;
int width;
String url;
Medium({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory Medium.fromJSON(Map<String, dynamic> MediumJson) {
return Medium(
height: MediumJson['height'],
width: MediumJson['width'],
url: MediumJson['url'],
);
}
}
class High {
int height;
int width;
String url;
High({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory High.fromJSON(Map<String, dynamic> HighJson) {
return High(
height: HighJson['height'],
width: HighJson['width'],
url: HighJson['url'],
);
}
}
class Default {
int height;
int width;
String url;
Default({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory Default.fromJSON(Map<String, dynamic> defaultJson) {
return Default(
height: defaultJson['height'],
width: defaultJson['width'],
url: defaultJson['url'],
);
}
}
class Thumbnails {
Default mDefault;
Medium medium;
High high;
Thumbnails({this.mDefault, this.medium, this.high});
var data = JsonEncoder().convert("");
Map<String, dynamic> toJson() => {
'default': mDefault,
'medium': medium,
'high': high,
};
factory Thumbnails.fromJSON(Map<String, dynamic> ThumbnailsJson) {
return Thumbnails(
mDefault: Default.fromJSON(ThumbnailsJson['default']),
medium: Medium.fromJSON(ThumbnailsJson['medium']),
high: High.fromJSON(ThumbnailsJson['high']),
);
}
}
Now that we have described the format of the JSON we expect it is very easy to parse it:
YoutubeResponse parsedResponse =
YoutubeResponse.fromJSON(json.decode(response.body));
You can then access the items via parsedResponse.items, then loop through them and get the title, descriptions etc.
To further the answer by #Shaddy (which works) here is my class that calls the network
class VideoNetwork {
static Future<List<Item>> fetchPost() async {
final response =
await http.get(<URL for Youtube Videos>);
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON
return compute(parseVideos, response.body);
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
static List<Item> parseVideos(String responseBody) {
YoutubeResponse response =
YoutubeResponse.fromJSON(json.decode(responseBody));
return response.items.toList();
}
}
Might be bit late to answer the question.
If you want to learn how to parse complex json data use these articles.
1. Parsing JSON in the background - Flutter cookbook
and
Parsing complex JSON in Flutter- medium.com by Pooja Bhaumik
FYI - Youtube data api response is in a nested Json format
I used a alternative way, this is not recommended for big applications(I was just playing around with it and this is only for above type json tree)
Use async function to retrieve and parse Json data.
final String dataUrl = "YOUR-JSON-URL";
Future<String> getdet() async {
var response = await http.get(Uri.encodeFull(dataUrl), headers: {"Accept": "application/json"});
if (response.statusCode == 200) {
var responseBody = json.decode(response.body);
convertedData = responseBody["items"];
} else {
throw Exception('Failed to Load Data');
}
"items " is the array starting point
after that you can use it in a Widget for example
Widget recentWidget(){
return ListView.builder(
itemCount: convertedData == null ? 0 : recent.length,
itemBuilder: (BuildContext context, int index, ) {
return Column(
children: <Widget>[
Card(
child: Column(
children: <Widget>[
new Image.network(recent[index]["snippet"]["thumbnails"]["medium"]["url"]),
new ListTile(
title: new Text(recent[index]["snippet"]["title"]),
subtitle: Text("This is Subtitle"),
},
dense: true,
),
],
),
)
],
);
},shrinkWrap: true,
physics: ClampingScrollPhysics(),
)
}
Hope this helps.
The answer of #Sh1d0w , was good but it has a lack of content for people who doesnt know so much of flutter itself. I have to say thanks Sh1d0w since you give me the base to create my own code to fetch the data using the youtube url.
I combined his answer using a SearchDelegate in order to show the results , I still need to add the code to show the next/previous page results (youtube only give 50 results by page) but to answer your questions here is the code.
At the end you will be able to get the follow:
publishedAt
channelId
title
description
thumbnails (default, high, medium)
liveBroadcastContent
videoId
If you want any other data from the json, you will need to add what you want in the Class Snippet to be returned and used later.
Since I am using a futureBuilder which give the data in the snapshot.data object, you will need to use this kind of declaration to get each property:
snapshot.data[index].title
snapshot.data[index].description
snapshot.data[index].thumbnails.high.url
**In the code you will see things like appTheme.text , etc those are just variables of colors, please change them for your colors.
** the functions like sizeBuild() or fontSizeBuild() are functions which I created for my case, you just need to delete those lines and write any number according to your needs
common_youtubeApi.dart
import 'dart:convert';
class YoutubeResponse{
//!-1st level parameters of youtube api for playlist
//!-https://developers.google.com/youtube/v3/docs/playlistItems/list#response
String kind;
String etag;
String nextPageToken;
String prevPageToken;
String regionCode;
List<Item> items;
//change the default values for the obtained values from url
YoutubeResponse({
this.kind,
this.etag,
this.nextPageToken,
this.prevPageToken,
this.regionCode,
this.items
});
//Json decode and make a dart object called Map
Map<String, dynamic> toJson() => {
'kind': kind,
'etag': etag,
'nextPageToken': nextPageToken,
'prevPageToken': prevPageToken,
'regionCode': regionCode,
'items': items,
};
factory YoutubeResponse.fromJSON(Map<String, dynamic> YoutubeResponseJson){
var list = YoutubeResponseJson['items'] as List;
List<Item> itemsList = list.map((i)=> Item.fromJSON(i)).toList();
return new YoutubeResponse(
kind: YoutubeResponseJson['kind'],
etag: YoutubeResponseJson['etag'],
nextPageToken: YoutubeResponseJson['nextPageToken'],
prevPageToken: YoutubeResponseJson['prevPageToken'],
regionCode: YoutubeResponseJson['regionCode'],
// mPageInfo: pageInfo.fromJSON(YoutubeResponseJson['pageInfo']),
items: itemsList
);
}
}
//---------Create an single video item
class Item{
String kind;
String etag;
String id;
Snippet snippet;
Item({
this.kind, this.etag, this.id, this.snippet
});
Map<String, dynamic> toJson() => {
'kind': kind,
'etag': etag,
'id': id,
'snippet': snippet,
};
factory Item.fromJSON(Map<String, dynamic> ItemJson) {
return Item(
kind: ItemJson['kind'],
etag: ItemJson['etag'],
id: ItemJson['id'],
snippet: Snippet.fromJSON(ItemJson['snippet']),
);
}
}
class Snippet {
String publishedAt;
String channelId;
String title;
String description;
Thumbnails thumbnails;
String channelTitle;
String liveBroadcastContent;
String videoId;
Snippet(
{this.publishedAt,
this.channelId,
this.title,
this.description,
this.thumbnails,
this.channelTitle,
this.liveBroadcastContent,
this.videoId,
});
Map<String, dynamic> toJson() => {
'publishedAt': publishedAt,
'channelId': channelId,
'title': title,
'description': description,
'thumbnails': thumbnails,
'channelTitle': channelTitle,
'liveBroadcastContent': liveBroadcastContent,
'videoId': videoId,
};
factory Snippet.fromJSON(Map<String, dynamic> snippetJson) {
return Snippet(
publishedAt: snippetJson['publishedAt'],
channelId: snippetJson['channelId'],
title: snippetJson['title'],
description: snippetJson['description'],
thumbnails: Thumbnails.fromJSON(snippetJson['thumbnails']) ,
channelTitle: snippetJson['channelTitle'],
liveBroadcastContent: snippetJson['liveBroadcastContent'],
videoId: snippetJson['resourceId']['videoId'],
);
}
}
class Medium {
int height;
int width;
String url;
Medium({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory Medium.fromJSON(Map<String, dynamic> MediumJson) {
return Medium(
height: MediumJson['height'],
width: MediumJson['width'],
url: MediumJson['url'],
);
}
}
class High {
int height;
int width;
String url;
High({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory High.fromJSON(Map<String, dynamic> HighJson) {
return High(
height: HighJson['height'],
width: HighJson['width'],
url: HighJson['url'],
);
}
}
class Default {
int height;
int width;
String url;
Default({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory Default.fromJSON(Map<String, dynamic> defaultJson) {
return Default(
height: defaultJson['height'],
width: defaultJson['width'],
url: defaultJson['url'],
);
}
}
class Thumbnails {
Default mDefault;
Medium medium;
High high;
Thumbnails({this.mDefault, this.medium, this.high});
var data = JsonEncoder().convert("");
Map<String, dynamic> toJson() => {
'default': mDefault,
'medium': medium,
'high': high,
};
factory Thumbnails.fromJSON(Map<String, dynamic> thumbnailsJson) {
return Thumbnails(
mDefault: Default.fromJSON(thumbnailsJson['default']),
medium: Medium.fromJSON(thumbnailsJson['medium']),
high: High.fromJSON(thumbnailsJson['high']),
);
}
}
searchList.dart
files to import:
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:denApp/keys/app_keys.dart'; //put your keys always in a different file with gitignore
import 'package:url_launcher/url_launcher.dart'; //for links in each item
import 'package:denApp/Widgets/common_youtubeAPI.dart'; //here are the models of each data requested in the json to be used here
class DataSearch extends SearchDelegate<List>{
var nextPageToken;
var prevPageToken;
Future<void> _launched;
//ajax/http request for data
Future<List<Snippet>> getVideos(http.Client client) async {
YoutubeResponse parsedResponse;
final response = await client
.get('https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=$youtubeTrickPlaylist&key=$youtubeAPIKEY&maxResults=50');
List<Snippet> videos = [];
parsedResponse = YoutubeResponse.fromJSON(json.decode(response.body));
for(var i=0; i < parsedResponse.items.length; i++ ){
videos.add(parsedResponse.items[i].snippet);
}
this.nextPageToken = parsedResponse.nextPageToken;
this.prevPageToken = parsedResponse.prevPageToken;
print(this.nextPageToken);
print(this.prevPageToken);
return videos;
}
//We use the launcher plugin to manage the click to go to the website, please visit the plugin web for info how to use it
Future<void> _launchInBrowser(String url) async {
print(url);
if (await canLaunch(url)) {
await launch(
url,
forceSafariVC: false,
forceWebView: false,
headers: <String, String>{'my_header_key': 'my_header_value'},
);
} else {
throw 'Could not launch $url';
}
}
//------------------------------------------------------
//--This part is to edit the colors and design of the searchDelegate widget, I have a separated file with the themes, colors which I call using appTheme, so you need to change all those variables with your own colors.
#override
ThemeData appBarTheme(BuildContext context) {
return ThemeData(
primaryColor: appTheme.text,
backgroundColor: appTheme.dark,
bottomAppBarColor: appTheme.dark,
canvasColor: appTheme.dark,
);
}
//-------------------------------------------------------------
//---Here we define how it will works the SearchDelegate and its icons/functions-----------
#override
List<Widget> buildActions(BuildContext context) {
// ----This is the icon which will delete whatever you write in your searchbar
return [IconButton(icon: Icon(Icons.clear), onPressed: () {
query="";
},)];
}
#override
Widget buildLeading(BuildContext context) {
// ---- This is the icon which will allow you to close the search delegate going to the back page.
return IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
close(context, null);
});
}
#override
Widget buildResults(BuildContext context) {
// nothing here
throw UnimplementedError();
}
#override
Widget buildSuggestions(BuildContext context) {
// This is actually the place where you will do all the work when you receive the data from your future request. Since we are going to build something after we wait for the data, we need to use futureBuilder instead of builder only. In the future propiety you will put the function which will do the request to the api
return FutureBuilder<List<Snippet>>(
future: getVideos(http.Client()),
builder: (context, snapshot) {
//during the build you need to check the connection state so depending on what is happening you will show something , like a loader if it is waiting state, etc
switch (snapshot.connectionState){
case ConnectionState.none:
return Text('none', style: TextStyle(color:Colors.black));
case ConnectionState.active:
return Text('active', style: TextStyle(color:Colors.black));
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
default:
//after checking connection we need to check if we do not got null data from the request
if(snapshot.data != null){
var dt = snapshot.data.where((p)=> p.title.toLowerCase().contains(query.toLowerCase())).toList();
return dt.isEmpty? Center(child:Text("There was no results for your query", style: TextStyle(color: appTheme.text))) : ListView.builder(
itemCount: dt.length,
padding: EdgeInsets.zero,
itemBuilder: (context, index) {
if(query.isEmpty){
return Card(
margin: (index == (dt.length - 1)) ? EdgeInsets.only(top: sizeBuild(context, "height", 5), bottom: sizeBuild(context, "height", 15)) : EdgeInsets.only(top: sizeBuild(context, "height", 5)),
color: appTheme.bgLight.withOpacity(.1),
child: InkWell(
splashColor: appTheme.linkB.withAlpha(30),
onTap: () {
_launched = _launchInBrowser('https://www.youtube.com/watch?v=${snapshot.data[index].videoId}');
},
child: Container(
child: Row(
children: <Widget>[
Container(
width:sizeBuild(context, "width", 140),
child: Image.network("${snapshot.data[index].thumbnails.high.url}", fit: BoxFit.cover),
),
Container(
// color: Colors.red,
margin: EdgeInsets.symmetric(horizontal: sizeBuild(context, "width",10)),
width:sizeBuild(context, "width", 280),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(snapshot.data[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle (
color: appTheme.text,
fontWeight: FontWeight.bold,
fontSize: fontSizeBuild(context, 'body')
),
),
SizedBox(
height: sizeBuild(context, "height", 5),
),
Text(snapshot.data[index].description,
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: TextStyle (
color: appTheme.text.withOpacity(.7),
fontSize: fontSizeBuild(context, 'small')
),
),
],
),
),
],
),
),
),
);
}else{
return Card(
margin: (index == (dt.length - 1)) ? EdgeInsets.only(top: sizeBuild(context, "height", 5), bottom: sizeBuild(context, "height", 15)) : EdgeInsets.only(top: sizeBuild(context, "height", 5)),
color: appTheme.bgLight.withOpacity(.1),
child: InkWell(
splashColor: appTheme.linkB.withAlpha(30),
onTap: () {
_launched = _launchInBrowser('https://www.youtube.com/watch?v=${snapshot.data[index].videoId}');
},
child: Container(
child: Row(
children: <Widget>[
Container(
width:sizeBuild(context, "width", 140),
child: Image.network("${snapshot.data[index].thumbnails.high.url}", fit: BoxFit.cover),
),
Container(
margin: EdgeInsets.symmetric(horizontal: sizeBuild(context, "width",10)),
width:sizeBuild(context, "width", 280),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(snapshot.data[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle (
color: appTheme.text,
fontWeight: FontWeight.bold,
fontSize: fontSizeBuild(context, 'body')
),
),
SizedBox(
height: sizeBuild(context, "height", 20),
),
Text(snapshot.data[index].description,
maxLines: 5,
overflow: TextOverflow.ellipsis,
style: TextStyle (
color: appTheme.text.withOpacity(.7),
fontSize: fontSizeBuild(context, 'small')
),
),
],
),
),
],
),
),
),
);
}
},
);
}else{
//this is the widget to give if there is no data
return Center(child: Text("There was no data from the server", style: TextStyle(color:appTheme.text)));
}
}
},
);
}
}
The problem in your code snippet is much more simple than the other answers would imply.
First of all, your JSON string might be wrong, you left out a couple of things. You might have also forgotten to unescape the etag values: "etag": "\\\"XI7nbFXulYBIpL0ayR_gDh3eu1k/s7-xmHXpuqQxYzDp_wxhm59K4LE\\\"",. Of course, all of these could be just copying errors.
You can create extra classes, and if you want to maintain your code for the long term, you absolutely should. There are code generators that will do most of the work for you. However, you don't need to create those humongous classes, just to get out 3 values of that JSON. You also don't need to spin up on a different isolate, that JSON is tiny (for the computer), and you won't notice any performance issues from parsing that.
There is nothing wrong with the approach you took. It's a simple solution that works, it's great for very simple scripts and apps.
The only thing is that you forgot that the items is a list, so you need to first take the 0th element in that list. After that fix, everything works as expected: parsed['items'][0]['snippet']['title'].
You can copy this to dartpad.dev and try it out yourself.
import 'dart:convert';
void main() {
final parsed = jsonDecode(responseBody);
String title = parsed['items'][0]['snippet']['title'];
String description = parsed['items'][0]['snippet']['description'];
String thumbnail = parsed['items'][0]['snippet']['thumbnails']['default']['url'];
print('title: $title');
print('description: $description');
print('thumbnail: $thumbnail');
}
const responseBody = '''{
"kind": "youtube#videoListResponse",
"etag": "\\\"XI7nbFXulYBIpL0ayR_gDh3eu1k/s7-xmHXpuqQxYzDp_wxhm59K4LE\\\"",
"pageInfo": {
"totalResults": 1,
"resultsPerPage": 1
},
"items": [
{
"kind": "youtube#video",
"etag": "\\\"XI7nbFXulYBIpL0ayR_gDh3eu1k/pajQ7iBy-7A0V_owifxkw-Kbw-Y\\\"",
"id": "7lCDEYXw3mM",
"snippet": {
"publishedAt": "2012-06-20T23:12:38.000Z",
"channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
"title": "Google I/O 101: Q&A On Using Google APIs",
"description": "Antonio Fuentes speaks to us and takes questions on working with Google APIs and OAuth 2.0.",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/default.jpg",
"width": 120,
"height": 90
},
"medium": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/mqdefault.jpg",
"width": 320,
"height": 180
},
"high": {
"url": "https://i.ytimg.com/vi/7lCDEYXw3mM/hqdefault.jpg",
"width": 480,
"height": 360
}
}
}
}
]
}''';
I want to serialize JSON objects to my native class objects from an incoming API request. However, when the mapping happens and the FutureBuilder tries to return data, it generates a runtime error as follows:
type _CastError is not a subtype of bool
while using a list to return data and
type _TypeError is not a subtype of bool
From the errors I am assuming that it is not able to cast the data from the stream. I don’t have many ideas as I am learning flutter.
Here is the future through which I am making a request:
Future getWeatherInfo(String city) async {
Uri uri = new Uri.https("api.apixu.com", "/v1/current.json",
{"key": <key>, "q": city});
print(uri);
final response = await http.get(uri);
final responseJSON = jsonDecode(response.body);
print(responseJSON);
return BaseClass.getData(responseJSON);
Native classes:
class BaseClass {
// final WeatherInformation weatherInformation;
final Weather weather;
BaseClass({this.weather});
factory BaseClass.getData(Map jsonData) {
return new BaseClass(
// weatherInformation: (jsonData["location"])
// .map((i) => WeatherInformation.weatherValues(i)),
weather: (jsonData["current"])
.map((i) => Weather.weatherData(i)),
);
}}
class WeatherInformation {
final String cityName;
final String cityRegion;
final String country;
final double lat;
final double long;
final String time;
WeatherInformation({
this.cityName,
this.cityRegion,
this.country,
this.time,
this.lat,
this.long,
});
factory WeatherInformation.weatherValues(Map data) {
return new WeatherInformation(
cityRegion: data['cityRegion'],
cityName: data['name'],
country: data['country'],
lat: data['lat'],
long: data['long'],
time: data['localtime'],
);
}
}
class Weather {
final Condition condition;
final double windmph;
final double windkph;
final double winddegree;
final int cloud;
final double pressuremb;
final double pressurein;
final double precipmm;
final double precipin;
final int humidity;
final double centrigade;
final double faranheit;
final double tempc;
final double tempf;
Weather({
this.centrigade,
this.cloud,
this.faranheit,
this.humidity,
this.pressuremb,
this.tempc,
this.precipmm,
this.precipin,
this.pressurein,
this.tempf,
this.winddegree,
this.windkph,
this.windmph,
this.condition,
});
factory Weather.weatherData(Map data) {
return new Weather(
tempc: data['temp_c'],
tempf: data['temp_f'],
windmph: data['wind_mph'],
windkph: data['wind_kph'],
winddegree: data['wind_degree'],
pressuremb: data['pressure_mb'],
pressurein: data['pressure_in'],
precipmm: data['precip_mm'],
precipin: data['precip_in'],
humidity: data['humidity'],
cloud: data['cloud'],
centrigade: data['feelslike_c'],
faranheit: data['feelslike_f'],
// condition:
// (data['condition'] as List).map((i) => Condition.toJSON(i)).toList(),
);
}
}
class Condition {
String text;
String icon;
Condition({
Key key,
this.text,
this.icon,
});
Condition.toJSON(Map data)
: icon = data['icon'],
text = data['text'];
}
ListView:
** if (snapshot.hasError)** { //(previously was snapshot.error)
return new Center(
child: new Chip(
label: new Text('Error! Please try again',style: new TextStyle(color:
Colors.white),)));
} else if (snapshot.hasData) {
return new ListView(
shrinkWrap:false,
children: <Widget>[
new ListTile(
leading: new Text("Calvin"),
title: new
Text(snapshot.data.weather[0].tempc.toString(),
style: new TextStyle(color: Colors.white)),),
new ListTile(
leading: new Text('Pressure'),
title: new Text(
snapshot.data.weather[0].pressurein.toString(),
style: new TextStyle(color: Colors.white)),
),
new ListTile(
title: new Text(snapshot.data.weather[0].cloud.toString()),
leading: new Icon(Icons.cloud)),
],
);
}
There is no type in the Future. Due to that, the AsyncSnapshot may not be working correctly.
Use Future<BaseClass> in the first listing above,
Then, in the FutureBuilder add it as follows:
new FutureBuilder<BaseClass>(
future: getWeatherInfo(...),
builder: (BuildContext context, AsyncSnapshot<BaseClass> snapshot) {
....