I want to update the screen whenever I call the API. Right now I have the following
Future<String> getData() async {
var response = await http.get(
Uri.parse('https://www.api_endpoint.com'),
headers: {
'Accept':'application/json'
}
);
Timer.periodic(Duration(microseconds: 1000), (_) {
this.setState(() {
data = json.decode(response.body);
print(data); //I can see this in the console/logcat
});
});
}
@override
void initState() {
this.getData();
}
from the line above print(data);
I can see the latest api responses in console/logcat but the screen doesn't update with the new values. I can't get my head around why the latest responses aren't shown on screen when this.setState()
is called every second with the Timer... all feedback is welcome. Thanks
CodePudding user response:
Future
executes once and returns just one
result. initState()
executed when creating a widget, this is also usually once. For your tasks it is better to use Streams
, my solution is not the best in terms of architecture, but as an example it works.
//We create a stream that will constantly read api data
Stream<String> remoteApi = (() async* {
const url = "http://jsonplaceholder.typicode.com/todos/1";
//Infinite loop is not good, but I have a simple example
while (true) {
try {
var response = await Dio().get(url);
if (response.statusCode == 200) {
//remote api data does not change, so i will add a timestamp
yield response.data.toString()
DateTime.now().millisecondsSinceEpoch.toString();
}
//Pause of 1 second after each request
await Future.delayed(const Duration(seconds: 1));
} catch (e) {
print(e);
}
}
})();
//On the screen we are waiting for data and display it on the screen
// A new piece of data will refresh the screen
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: StreamBuilder<String>(
stream: remoteApi,
builder: (
BuildContext context,
AsyncSnapshot<String> snapshot,
) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.connectionState == ConnectionState.active ||
snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return const Text('Error');
} else if (snapshot.hasData) {
return Center(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Text(
snapshot.data.toString(),
textAlign: TextAlign.center,
),
),
);
} else {
return const Center(child: Text('Empty data'));
}
} else {
return Center(child: Text('State: ${snapshot.connectionState}'));
}
},
),
);
}
Or simplest solution
Future<String> remoteApi() async {
try {
const url = "http://jsonplaceholder.typicode.com/todos/1";
var response = await Dio().get(url);
if (response.statusCode == 200) {
return response.data.toString()
DateTime.now().millisecondsSinceEpoch.toString();
} else {
throw ("Error happens");
}
} catch (e) {
throw ("Error happens");
}
}
var displayValue = "Empty data";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.all(15.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(child: Text(displayValue)),
Center(
child: ElevatedButton.icon(
onPressed: () async {
displayValue = await remoteApi();
setState(() {});
},
label: const Text('Get API'),
icon: const Icon(Icons.download),
),
)
],
),
));
}
CodePudding user response:
Ah, you don't actually call your API every timer tick, you just decode the same body from the first call.
If you want to call your API periodically, you need to move the actual http.get call inside the timer method.