Related
I am trying to get the data from a json file. But I can't done it right. I don't know what is wrong with this. Hope to get an advice or tutorial.
The error I get is : Undefined name 'breakfast'.
Then when I change breakfast to Breakfast, I got the error : Instance member '...' can't be accessed using static access. Nothing else. I hope to get an explanation. I'm just a newbie to flutter. I tried to look for the problem and explanation to the web but nothing fix it.
Here is the code:
```import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:models/Breakfast.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../constants (2).dart';
import '../constants.dart';
import '../size_config.dart';
class BreakfastCard extends StatefulWidget {
const BreakfastCard({
Key? key,
this.width = 140,
this.aspectRetio = 1.02,
required this.breakfast,
}) : super(key: key);
final double`enter code here` width, aspectRetio;
final Breakfast breakfast;
#override
_BreakfastCardState createState() => _BreakfastCardState();
}
class _BreakfastCardState extends State<BreakfastCard> {
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return FutureBuilder(
future: loadBreakfast(),
builder: (BuildContext, AsyncSnapshot<dynamic>snapshot){
return Padding(
padding: EdgeInsets.only(left: getProportionateScreenWidth(20)),
child: SizedBox(
width: getProportionateScreenWidth(140),
child: GestureDetector(
onTap: (){},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: 1.02,
child: Container(
padding: EdgeInsets.all(getProportionateScreenWidth(20)),
decoration: BoxDecoration(
color: kSecondaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(15),
),
child: Hero(
tag: breakfast.id.toString(),
child: Image.asset(breakfast.images[0]),
),
),
),
const SizedBox(height: 10),
Text(
breakfast.title,
style: const TextStyle(color: Colors.black),
maxLines: 2,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"${breakfast.calories} cal |",
style: TextStyle(
fontSize: getProportionateScreenWidth(18),
fontWeight: FontWeight.bold,
color: kPrimaryColor,
),
),
Text(
"${breakfast.time} min",
style: TextStyle(
fontSize: getProportionateScreenWidth(18),
fontWeight: FontWeight.w600,
color: kPrimaryColor,
),
),
InkWell(
borderRadius: BorderRadius.circular(50),
onTap: () { breakfast.isFavorite = !breakfast.isFavorite;},
child: Container(
padding: EdgeInsets.all(getProportionateScreenWidth(8)),
height: getProportionateScreenWidth(28),
width: getProportionateScreenWidth(28),
child: SvgPicture.asset(
"assets/icons/Heart Icon_2.svg",
color: breakfast.isFavorite
? const Color(0xFFFF4848)
: const Color(0xFFDBDEE4),
),
),
),
],
)
],
),
),
),
);
}
);
}
Future<String> _loadloadBreakfastAsset() async {
return await rootBundle.loadString('assets/data.json');
}
Future loadBreakfast() async {
String jsonAddress = await _loadloadBreakfastAsset();
final jsonResponse = json.decode(jsonAddress);
Breakfast breakfast = Breakfast.fromJson(jsonResponse);
}
}```
This is the Model
class Breakfast {
final int id, time, serving;
final String title, description, calories;
final List <String> procedure;
final List <String> ingredients;
final List <String> naturalFacts;
final List<String> images;
final double rating;
bool isFavorite, isPopular;
Breakfast({
required this.id,
required this.images,
this.rating = 0.0,
this.isFavorite = false,
this.isPopular = false,
required this.title,
required this.time,
required this.description,
required this.ingredients,
required this.procedure,
required this.naturalFacts,
required this.calories,
required this.serving,
});
factory Breakfast.fromJson(Map<String, dynamic> parsedJson) {
var procedureFromJson = parsedJson['procedure'];
var ingredientsFromJson = parsedJson['ingredients'];
var naturalFactsFromJson = parsedJson['naturalFacts'];
var imagesFromJson = parsedJson['images'];
List<String> ingredientsList = ingredientsFromJson.cast<String>();
List<String> procedureList = procedureFromJson.cast<String>();
List<String> imagesList = imagesFromJson.cast<String>();
return new Breakfast(
calories: parsedJson['calories'],
time: parsedJson['time'],
title: parsedJson['title'],
description: parsedJson['description'],
naturalFacts: parsedJson['naturalFacts'],
serving: parsedJson['serving'],
id: parsedJson['id'],
procedure: procedureList,
ingredients: ingredientsList,
images: imagesList,
);
}
}
The json data
[
{
"id": 1,
"rating": 0.0,
"images": [
"assets/images/cilantro.png"
],
"title": "Cilantro and Kale Pesto Toast with a Fried Egg",
"time": 15,
"description": "Sliced bread is the perfect blank canvas, ready to be loaded up with virtuous ingredients.",
" rating": 4.8,
"isFavorite": false,
"isPopular": true,
"calories": "405",
"serving": 1,
"naturalFacts": [
"405 calories",
"protein 15g",
"fat 31g",
"saturated fat 5.8g",
"carbohydrates 16g",
"fiber 1.9g",
"sodium 331mg",
"cholesterol 189mg"
],
"ingredients": [
"¼ cup packed cilantro",
"1 cup packed kale leaves",
"¼ cup extra-virgin olive oil",
"1 tablespoon white balsamic vinegar",
"2 tablespoons hulled hemp seeds*",
"salt",
"Freshly ground pepper",
"1 large slice of whole-wheat toast",
"2 tablespoons unflavored whole-milk Greek yogurt",
"1 fried egg"
],
"procedure": [
"Whirl the cilantro, kale leaves, extra-virgin olive oil, white balsamic vinegar, and hemp seeds* until fairly smooth, scraping inside of bowl.",
"Season with sea salt and freshly ground pepper. Smear a large slice of whole-wheat toast with the yogurt, then with some pesto.",
"Top with a fried egg and more salt and pepper."
]
}
]
From just my visual inspection of your code, I would say start by fixing up your loadBreakfast() method.
Your original code...
Future loadBreakfast() async {
String jsonAddress = await _loadloadBreakfastAsset();
final jsonResponse = json.decode(jsonAddress);
Breakfast breakfast = Breakfast.fromJson(jsonResponse);
}
A Future is Dart's name for a Promise()
It's better if the method returns a type of future. Your method doesn't return anything at all. When your FutureBuilder calls loadBreakfast, it's probably receiving a null response and no work is getting done.
This change fixes the Future's return issue:
Future<Breakfast> loadBreakfast() async {
String jsonAddress = await _loadloadBreakfastAsset();
final jsonResponse = json.decode(jsonAddress);
// This line is a problem. See my later comments.
// Breakfast breakfast = Breakfast.fromJson(jsonResponse);
return Future<Breakfast>.value(Breakfast.fromJson(jsonResponse));
}
This line is not doing what you think it is.
Breakfast breakfast = Breakfast.fromJson(jsonResponse);
This declares a new variable named 'breakfast', assigns the value of the parsed JSON to it, then the method finishes and throws the variable away.
BTW, this is an example of code that relies on side effects, and you should rethink how you do this.
Nothing changes the value you declared at the beginning of the method.
Here is how to get the effect you want.
I'm going to assume that you want to assign the parsed JSON return to the 'breakfast' property you defined in the top of the widget. If so, this is how you should do it.
First, move the two methods at the bottom of the file into the scope of the _BreakfastCardState class.
}<--- move this brace past the end of loadBreakfast()
Future<String> _loadloadBreakfastAsset() async {
return await rootBundle.loadString('assets/data.json');
}
Future loadBreakfast() async {
String jsonAddress = await _loadloadBreakfastAsset();
final jsonResponse = json.decode(jsonAddress);
Breakfast breakfast = Breakfast.fromJson(jsonResponse);
}
This will allow these methods to access the breakfast variable in the StatefulWiget's scope.
Now you would like to change loadBreakfast() into this form.
Future<Breakfast> loadBreakfast() async {
String jsonAddress = await _loadloadBreakfastAsset();
final jsonResponse = json.decode(jsonAddress);
// This now updates the breakfast property in the main class.
widget.breakfast = Breakfast.fromJson(jsonResponse);
// This return value is thrown away, but this line is necessary to
// resolve the Future call that FutureBuilder is waiting on.
return Future<Breakfast>.value(null);
}
The final effect should be that the FutureBuilder waits until you parse your JSON file and update 'breakfast' with its parsed value, then you resolve the Future.
FutureBuilder then calls your predicate code, and the return should have all the values you expect.
ONE MORE THING: Everywhere in your Widget code that you have 'breakfast.', change that to 'widget.breakfast.'
If this works out for you, please mark this as the accepted answer.
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 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'm trying to do a Dashboard with multiples jsons requests, but i want that request work one per one, like when finish first request start the second, when finish second start the third when finish third start the N.
my list code:
new CustomScrollView(
cacheExtent: height * 6,
slivers: [
new SliverList(
delegate: new SliverChildListDelegate(
[
new RelatorioVendPeriodoAPeriodo(),
new RelatorioMensals(),
new RelatorioDiasDaSemanas(),
new RelatorioVendasTotalidasPorPeriodo(),
new RelatorioDasVendasTotsProdutos(),
]
)
)
]
),
this new classes calls, returns for me request. Anyone knows how to delay it?
First, the parent widget should return a progress bar when the necessary data is not available.
A service will be called an initState to fetch data from the backend. when data is ready setState() will be called to redraw the widget.
Look at this example:
class _TestWidgetState extends State<TestWidget> {
var data;
#override
void initState() {
data = NetworkService.getData().then((data) {
setState(() {
this.data = data;
});
});
}
#override
Widget build(BuildContext context) {
if (data == null) {
return CircularProgressIndicator();
} else {
return
new CustomScrollView(
cacheExtent: height * 6,
slivers: [
new SliverList(
delegate: new SliverChildListDelegate(
[
new RelatorioVendPeriodoAPeriodo(data: data),
new RelatorioMensals(data: data),
new RelatorioDiasDaSemanas(data: data),
new RelatorioVendasTotalidasPorPeriodo(data: data),
new RelatorioDasVendasTotsProdutos(data: data),
]
)
)
]
);
}
}
}
class NetworkService {
final JsonDecoder _decoder = new JsonDecoder();
static String data1;
static String data2;
static getData() async {
if (data1 == null || data2 == null) {
await fetchFromServer();
}
return {'data1': data1, 'data2': data2};
}
static fetchFromServer() async {
data1 = (await http.get('url')).body;
data2 = (await http.get('url')).body;
}
}
Future.delayed(const Duration(milliseconds: 500), () {
print(" This line is executed after 5 seconds");
});
I'm successfully printing my response as String from my YouTube JSON url, but when I try to serialize through the "items" I get the following error Unhandled exception:
type 'List' is not a subtype of type 'Map' of 'json' where
List is from dart:core
Map is from dart:core
Here is my code...
class CardInfo {
//Constructor
String id;
String description;
String role;
//int score;
CardInfo.fromJson(Map json) {
this.id = json['vieoId'];
this.description = json['description'];
this.role = json['title'];
//this.score = json['score'];
}
}
Future getData() async {
String url = 'YouTube url';
var httpClient = createHttpClient();
var response = await httpClient.get(url);
Map data = JSON.decode(response.body);
//String ip = data['items'];
var ci = new CardInfo.fromJson(data['items']);
//print(data['items']);
print(ci.id);
//print(ci.description);
//print(ci.role);
if (!mounted) return;
setState(() {});
}
print(data['items'] is printing, but print(ci.id) or any Card Info variables throws the above error.
**** Log of print(data);
{kind: youtube#searchListResponse, etag: "VPWTmrH7dFmi4s1RqrK4tLejnRI/P9wyOxsXEuXOCvj7znCun2-EykU", nextPageToken: CAMQAA, regionCode: US, pageInfo: {totalResults: 1000000, resultsPerPage: 3}, items: [{kind: youtube#searchResult, etag: "VPWTmrH7dFmi4s1RqrK4tLejnRI/Csl1kQhnOsbs0j4_336zJAN176k", id: {kind: youtube#video, videoId: e3pUxU_bE6w}, snippet: {publishedAt: 2017-09-14T09:43:17.000Z, channelId: UCbD8EppRX3ZwJSou-TVo90A, title: [PRISTIN - We Like] KPOP TV Show | M COUNTDOWN 170914 EP.541, description: KPOP Chart Show M COUNTDOWN | EP.541 - PRISTIN - We Like ▷Watch more video clips: http://MCOUNTDOWN-KPOP2017 [Kor Ver.] 프리티 ..., thumbnails: {default: {url: https://i.ytimg.com/vi/e3pUxU_bE6w/default.jpg, width: 120, height: 90}, medium: {url: https://i.ytimg.com/vi/e3pUxU_bE6w/mqdefault.jpg, width: 320, height: 180}, high: {url: https://i.ytimg.com/vi/e3pUxU_bE6w/hqdefault.jpg, width: 480, height: 360}}, channelTitle: Mnet K-POP, liveBroadcastContent: none}}, {kind: youtube#searchResult, etag: "VPWTmrH7dFmi4s1RqrK4tLejnRI/1JCCNBPNbFeusCp_9-pl4i8q5OU", id: {kind: youtube#video, videoId: Cc4hO9RLdl4}, snippet: {publishedAt: 2017-09-14T10:37:29.000Z, channelId: UCbD8EppRX3ZwJSou-TVo90A, title: [EXO - Power] KPOP TV Show | M COUNTDOWN 170914 EP.541, description: KPOP Chart Show M COUNTDOWN | EP.541 - EXO - Power ▷Watch more video clips: http://MCOUNTDOWN-KPOP2017 [Kor Ver.] Power Up! '#EXO' 여기 ..., thumbnails: {default: {url: https://i.ytimg.com/vi/Cc4hO9RLdl4/default.jpg, width: 120, height: 90}, medium: {url: https://i.ytimg.com/vi/Cc4hO9RLdl4/mqdefault.jpg, width: 320, height: 180}, high: {url: https://i.ytimg.com/vi/Cc4hO9RLdl4/hqdefault.jpg, width: 480, height: 360}}, channelTitle: Mnet K-POP, liveBroadcastContent: none}}, {kind: youtube#searchResult, etag: "VPWTmrH7dFmi4s1RqrK4tLejnRI/ZnYC4e5evyfldkM67HsDuV8Yh3E", id: {kind: youtube#video, videoId: BBcOM25wrVo}, snippet: {publishedAt: 2017-08-18T15:21:48.000Z, channelId: UCtFtO4By4czgkYGvEXvJu0A, title: Kpop Banned Dance: MV vs LIVE, description: Kpop Banned Dance: MV vs LIVE Koreas biggest broadcasting companies has strict rules and standards on what lyrics and dances moves can be performed., thumbnails: {default: {url: https://i.ytimg.com/vi/BBcOM25wrVo/default.jpg, width: 120, height: 90}, medium: {url: https://i.ytimg.com/vi/BBcOM25wrVo/mqdefault.jpg, width: 320, height: 180}, high: {url: https://i.ytimg.com/vi/BBcOM25wrVo/hqdefault.jpg, width: 480, height: 360}}, channelTitle: Kpop Corn, liveBroadcastContent: none}}]}
*** UPDATE WITH FOR LOOP STATEMENT
Here is code for my for loop that's returning a type 'String' is not a subtype of type 'int' of 'index' error...
Map data = JSON.decode(response);
var videos = data['items'];
for (var items in videos['snippet']){
print(items);
}
Running a loop through items in videos gives me 3 separate entries for the 3 videos I'm looking for - including snippets. Trying to get the individual snippets is failing. Please point me in the right direction.
It looks like data['items'] is a List (i.e. a JSON Array), not a Map.
You can use list comprehension methods to help here:
final items = (data['items'] as List).map((i) => new CardInfo.fromJson(i));
for (final item in items) {
print(item.id);
}
The following line gives you the List of items.
var videos = data['items'];
and you get the error because of this line
for(var items in videos['snippet'])
In the previous line you think you are iterating on the data inside snippet, while in fact, you are trying to iterate on the index 'snippet' inside the list of videos, which does not make sense because iterating over any list happens using integer values videos[0] , videos [1], videos [2] .. while you are passing a String 'snippet'
You need first to iterate on your videos list item by item (each item is a Map). Store each Map in a variable. then you can access the values of snippet by myMap['snippet']
Map data = JSON.decode(response);
var videos = data['items']; //returns a List of Maps
for (var items in videos){ //iterate over the list
Map myMap = items; //store each map
print(myMap['snippet']);
}
See if this solves your problem.
I would Love to share this and some expert can also please improve this codes, After alot of hours have battle with it.
Model Class
class Testimony{
String fullname;
String testimony;
Testimony({this.fullname,
this.testimony});
factory Testimony.fromJson(Map<String, dynamic> json) => new Testimony(
fullname: json['fullname'] as String,
testimony: json['testimony'] as String,
);
}
API CLASS
List<Testimony> ToListandMap (String responseBody) {
Map data = json.decode(responseBody);
var videos = data['testimonies']; //returns a List of Maps
final casting = videos.cast<Map<String, dynamic>>();
return casting.map<Testimony>((json) => Testimony.fromJson(json)).toList();
}
Future<List<Testimony>> fetchTestimonies(http.Client client) async {
final response = await client.get('https://tryjambcbt.com/api/testimonies');
return ToList(response.body);
}
MainWidget for UI
FutureBuilder<List<Testimony>>(
future: fetchTestimonies(http.Client()),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? TestimonyList(testimony: snapshot.data)
: Center(child: CircularProgressIndicator());
},
),
Widget
class TestimonyList extends StatelessWidget {
final List<Testimony> testimony;
TestimonyList({Key key, this.testimony}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
physics: BouncingScrollPhysics(),
padding: EdgeInsets.only(bottom: 10),
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: testimony.length,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.only(right: 10),
child: Text(testimony[index].testimony)
);
},
);
}
}