Fetch Api Data Automatically with Interval in Flutter - json

On my flutter application I am trying to show updating data. I am successful in getting data from weather api manually. But I need to constantly grab data every 5 seconds. So it should be updated automatically. Here is my code in Flutter :
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sakarya Hava',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Sakarya Hava'),
),
body: Center(
child: FutureBuilder<SakaryaAir>(
future: getSakaryaAir(), //sets the getSakaryaAir method as the expected Future
builder: (context, snapshot) {
if (snapshot.hasData) { //checks if the response returns valid data
return Center(
child: Column(
children: <Widget>[
Text("${snapshot.data.temp}"), //displays the temperature
SizedBox(
height: 10.0,
),
Text(" - ${snapshot.data.humidity}"), //displays the humidity
],
),
);
} else if (snapshot.hasError) { //checks if the response throws an error
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
),
),
),
);
}
Future<SakaryaAir> getSakaryaAir() async {
String url = 'http://api.openweathermap.org/data/2.5/weather?id=740352&APPID=6ccf09034c9f8b587c47133a646f0e8a';
final response =
await http.get(url, headers: {"Accept": "application/json"});
if (response.statusCode == 200) {
return SakaryaAir.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load post');
}
}
}
I found such a snippet to benefit from :
// runs every 5 second
Timer.periodic(new Duration(seconds: 5), (timer) {
debugPrint(timer.tick);
});
Probably I need to wrap and call FutureBuilder with this snippet but I was not able to understand how to do it.

Futures can have 2 states: completed or uncompleted. Futures cannot "progress", but Streams can, so for your use case Streams make more sense.
You can use them like this:
Stream.periodic(Duration(seconds: 5)).asyncMap((i) => getSakaryaAir())
periodic emits empty events every 5 seconds and we use asyncMap to map that event into another stream, which get us the data.
Here is working example:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class ExamplePage extends StatelessWidget {
Future<String> getSakaryaAir() async {
String url =
'https://www.random.org/integers/?num=1&min=1&max=6&col=1&base=10&format=plain&rnd=new';
final response =
await http.get(url, headers: {"Accept": "application/json"});
return response.body;
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: Stream.periodic(Duration(seconds: 5))
.asyncMap((i) => getSakaryaAir()), // i is null here (check periodic docs)
builder: (context, snapshot) => Text(snapshot.data.toString()), // builder should also handle the case when data is not fetched yet
);
}
}

You can refactor your FutureBuilder to use a Future variable instead of calling the method in the FutureBuilder. This would require you to use a StatefulWidget and you can set up the future in your initState and update it by calling setState.
So you have a future variable field like:
Future< SakaryaAir> _future;
So your initState would look like this :
#override
void initState() {
super.initState();
setUpTimedFetch();
}
where setUpTimedFetch is defined as
setUpTimedFetch() {
Timer.periodic(Duration(milliseconds: 5000), (timer) {
setState(() {
_future = getSakaryaAir();
});
});
}
Finally, your FutureBuilder will be changed to:
FutureBuilder<SakaryaAir>(
future: _future,
builder: (context, snapshot) {
//Rest of your code
}),
Here is a DartPad demo: https://dartpad.dev/2f937d27a9fffd8f59ccf08221b82be3

Related

Http request does not complete

I'm trying to get data from a csv file on the internet:
https://covid.ourworldindata.org/data/owid-covid-data.csv
If you clicked on it you might have noticed that it downloads automatically, which I think is the problem.
When I'm using the http library in dart to get this data, I only get back a small part of it at the beginning. The response.statusCode is still equal to 200 and everything else works as expected.
I think that the automatic download is part of the problem but I'm not sure how to fix that
Here is the code:
import 'package:http/http.dart' as http;
...
main(){
String url = 'https://covid.ourworldindata.org/data/owid-covid-data.csv';
http.Response response = await http.get(url);
print(response.body);
}
Here is an example with json format:
Future<Response> fetchData() async
{
return await http.get("https://covid.ourworldindata.org/data/owid-covid-data.json");
}
Then inside the build() method:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: FutureBuilder(
future: fetchData(),
builder: (context, AsyncSnapshot<Response> snapshot) {
if (snapshot.hasData) {
Map<String, dynamic> map =
Map.from(jsonDecode(snapshot.data.body));
return Text(map["LBN"]["continent"]);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return CircularProgressIndicator();
},
), // This trailing comma makes auto-formatting nicer for build methods.
));
}

Flutter. Add a json.decode value to a String that can be used in the UI

Maybe this is a simple question, but I can't find the answer to it.
My app has 2 screens. 1st has a single button
onPressed: () {
fetchCurrentTitle();
Navigator.push(context,
MaterialPageRoute(builder: (context) => Screen2Widget()));
},
fetchCurrentTitle() method fetches data from json and decodes it.
I can see the return using:
final streamFullTitle = json.decode(response.body)['data'][0]['title'];
print(streamFullTitle);
I get the desired response of the current title in the console.
In the 2nd screen I have a hardcoded List. Where items have these values:
class List {
String id;
String streamer;
String logoUrl;
String title;
}
The first three attribute in List class dont need to change so they are hardcoded. I just need to assign the title value fromfetchCurrentTitle() to the String title. in class List.
Look of one of my list items
My fetchCurrentTitle() works as intended
Future<String> fetchCurrentTitle() async {
http.Response response = await http.get(...
I want the user to push the button on the first screen to go to the second screen and show a spinner with title "looking for title" and then get the new title instead of waiting fetchCurrentTitle() to complete only entering the second screen.
Thank you in advance.
You can try to run your fetchCurrentTitle() in the second screen on
void initState() {
super.initState();
fetchCurrentTitle()
/// show or set visibility for loading spinner
}
After you get the title value, you can simply assign new title by
List existingList = new List(id,streamer,logoUrl,title);
existingList.id = titleValueFromApi
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:sample_project_for_api/Employee.dart';
void main() => runApp(MaterialApp(
title: "App",
home: MyApp(),
));
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('home page'),
),
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: RaisedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => SecondScreen()));
},
child: const Text('First Page')),
)),
);
}
}
class SecondScreen extends StatefulWidget {
#override
_SecondScreenState createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
bool _isLoading = false;
Employee sampleData;
#override
void initState() {
super.initState();
getYouData();
}
Future<String> loadPersonFromAssets() async {
return await rootBundle.loadString('json/parse.json');
}
getYouData() async {
setState(() {
_isLoading = true;
});
String jsonString = await loadPersonFromAssets();
final data = employeeFromJson(jsonString);
sampleData = data;
// this is where you get the data from the network
Future.delayed(const Duration(seconds: 5), () {
// this is sample delay for you to know the delay.
// you can say this is loading your data
setState(() {
_isLoading = false;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Container(
child: _isLoading
//here give your message getting the title
? CircularProgressIndicator()
: Card(
child: Column(
children: <Widget>[
Text('your 1st hardcoded text'),
Text('your 2st hardcoded text'),
Text('your 3st hardcoded text'),
Text(
'This is your dynamic data after fetching :${sampleData.employeeName}')
],
),
),
),
),
),
);
}
}
check out this example you will give you an idea of what to do
1) going from one page to another page
2) loading you data , while that showing the spinner
3) on fetching the data update the UI.
let me know about this.
Thanks.
Try this,
import 'dart:convert';
import "package:flutter/material.dart";
import 'package:http/http.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: Page1(),
);
}
}
class Page1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Page 1")),
body: Center(
child: RaisedButton(
child: Text("Goto Page2"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Page2(),
),
);
},
),
),
);
}
}
class Page2 extends StatefulWidget {
#override
_Page2State createState() => _Page2State();
}
class _Page2State extends State<Page2> {
Future<void> _initLoader;
String _title;
#override
void initState() {
_initLoader = _loadInitData();
super.initState();
}
Future<void> _loadInitData() async {
await Future.delayed(Duration(seconds: 2));
//_title = await fetchCurrentTitle();
_title = "This is the Loaded Title";
}
Future<String> fetchCurrentTitle() async {
Response response = await get("...");
return json.decode(response.body)['data'][0]['title'];
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Page 2")),
body: FutureBuilder(
future: _initLoader,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
CircularProgressIndicator(),
const SizedBox(height: 8.0),
Text("Looking for Title"),
],
),
);
else if (snapshot.hasError)
return Center(
child: Text("Error: ${snapshot.error}"),
);
else
return Center(
child: Text("$_title"),
);
},
),
);
}
}

JSON Array to JSON Object

I'm using a HTTP request that gets a JSON array and pushes this into a JSON object which is read by a list view. I'm having difficulty forcing the JSON array into a JSON object so I'm currently calling each object once via json.decode(response.body)[0]. How can I cast the JSON Array to a JSON Object and have the list view read this entire JSON object?
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Post> fetchPost() async {
final url = <my_url>;
final response =
await http.get(url);
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON.
print(json.decode(response.body));
// TODO: Identify a way to convert JSON Array to JSON Object
return Post.fromJson(json.decode(response.body)[0]);
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
class Post {
final String title;
Post({this.title});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
title: json['title']
);
}
}
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Future<Post> post;
#override
void initState() {
super.initState();
post = fetchPost();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<Post>(
future: post,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.title);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return CircularProgressIndicator();
},
),
),
),
);
}
}
try this,
Future<List<Post>> fetchPost() async {
final url = <my_url>;
final response =
await http.get(url);
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON.
print(json.decode(response.body));
List<dynamic> responseList = json.decode(response.body);
// TODO: Identify a way to convert JSON Array to JSON Object
List<Post> tempList = [];
responseList.forEach((f) {
tempList.add(Post.fromJson(f));
});
return tempList;
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
class Post {
final int id;
final String title;
Post({this.id, this.title});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(id: json['id'], title: json['title']);
}
}
class _Frag_CommitteeState extends State<Frag_Committee> {
Future<List<Post>> post;
#override
void initState() {
super.initState();
post = fetchPost();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<List<Post>>(
future: post,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return Text(snapshot.data[index].title);
});
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return CircularProgressIndicator();
},
),
),
);
}
}

Flutter app using pagewise package and JSON

I am building an APP using pagewise package and would like to get the totalCount var from an external JSON request.
I have a function that returns an INT value that will be the totalCount but when added to the totalCount parameter it returns an error:
type 'Future<int>' is not a subtype of type 'int'
How can I solve this matter?
UPDATE:
return PagewiseGridView(
pageSize: 6,
totalCount: getTotals(),
crossAxisCount: 2,
mainAxisSpacing: 8.0,
crossAxisSpacing: 8.0,
childAspectRatio: 0.555,
padding: EdgeInsets.all(15.0),
itemBuilder: this._itemBuilder,
pageFuture: BackendService.getPage,
);
this is the class that creates the grid.
The problem is that you can't pass a future to a variable that expects an int.
You need to await for the future to complete and while you are awaiting you could, for instance, display a centered circular indicator.
This is something that you could use in your State class:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
State createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _totalCounter = -1;
#override
void initState() {
super.initState();
getTotalCounter();
}
void getTotalCounter() async {
_totalCounter = await getTotals();
setState(() {});
}
// Simulate your future
Future<int> getTotals() {
return Future.delayed(Duration(seconds: 3), () => 100);
}
#override
Widget build(BuildContext context) {
return _totalCounter == -1
? Center(child: CircularProgressIndicator())
: PagewiseGridView(
pageSize: 6,
totalCount: _totalCounter,
crossAxisCount: 2,
mainAxisSpacing: 8.0,
crossAxisSpacing: 8.0,
childAspectRatio: 0.555,
padding: EdgeInsets.all(15.0),
itemBuilder: this._itemBuilder,
pageFuture: BackendService.getPage,
);
}
}
From the dart documentation:
A future is a Future object, which represents an asynchronous operation that produces a result of type T.
In your case it is something you need access the info after it was retrieved async.
// Performing a request
// ... some widget
// ... child/Center is here just to exemplify how to use this Future inside a widget
// child: Center(child:
FutureBuilder<CustomList>(
future: fetchPost(),
builder: (context, snapshot) {
if (snapshot.hasData) {
// USE HERE THE DATA:
// snapshot.data.allCustoms
// snapshot.data.allCustoms.length
// for example you can create a ListView here
enter code here
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
})
// Making a request example (called above)
Future<CustomList> fetchPost() async {
final response = await http
.get('https://some_api_example');
if (response.statusCode == 200) {
// was successful, parse
return CustomList.fromJson(json.decode(response.body));
} else {
// not successful, throw.
throw Exception('Failed to load post');
}
}
// Some custom object we need to parse
class Custom {
final String id;
final String info;
Custom({this.id, this.info});
factory Custom.fromJson(Map<String, dynamic> json) {
return Custom(
id: json['id'].replaceAll(" ", ""),
info: json['info'].replaceAll(" ", "")
);
}
}
// A list of custom objects we parse from the reqeust
class CustomList {
final List<Custom> allCustoms;
CustomsList({
this.allCustoms,
});
factory CustomList.fromJson(List<dynamic> parsedJson) {
allCustoms = new List<Custom>();
allCustoms = parsedJson.map((i) => Custom.fromJson(i)).toList();
return new CustomList(
allCustoms: allCustoms,
);
}
}
Could you please try the following code and let me know what error are you getting.
return FutureBuilder<CustomList>(
future: fetchPost(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return PagewiseGridView(
pageSize: 6,
totalCount: snapshot.data,
crossAxisCount: 2,
mainAxisSpacing: 8.0,
crossAxisSpacing: 8.0,
childAspectRatio: 0.555,
padding: EdgeInsets.all(15.0),
itemBuilder: this._itemBuilder,
pageFuture: BackendService.getPage,
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
})

How to show CircularProgressIndicator before Flutter App Start?

In my demo app I need to load a 2 JSON file from server. Both JSON has large data in it. I my Flutter app I call the json using Future + async + await than I call to create a widget with runApp. In the body I try to activate a CircularProgressIndicator. It shows appBar and its content as well as empty white page body and 4 or 5 seconds later loads the data in actual body.
My question is I need to show the CircularProgressIndicator first and once the data load I will call runApp(). How do I do that?
// MAIN
void main() async {
_isLoading = true;
// Get Currency Json
currencyData = await getCurrencyData();
// Get Weather Json
weatherData = await getWeatherData();
runApp(new MyApp());
}
// Body
body: _isLoading ?
new Center(child:
new CircularProgressIndicator(
backgroundColor: Colors.greenAccent.shade700,
)
) :
new Container(
//… actual UI
)
You need to put the data/or loading indicator inside a scaffold, show the scaffold everytime whether you have data or not, the content inside you can then do what you want to do.`
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Hello Rectangle',
home: Scaffold(
appBar: AppBar(
title: Text('Hello Rectangle'),
),
body: HelloRectangle(),
),
),
);
}
class HelloRectangle extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Container(
color: Colors.greenAccent,
height: 400.0,
width: 300.0,
child: Center(
child: FutureBuilder(
future: buildText(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return CircularProgressIndicator(backgroundColor: Colors.blue);
} else {
return Text(
'Hello!',
style: TextStyle(fontSize: 40.0),
textAlign: TextAlign.center,
);
}
},
),
),
),
);
}
Future buildText() {
return new Future.delayed(
const Duration(seconds: 5), () => print('waiting'));
}
}
`