Reputation: 359
I am new in flutter I need to know I am following the document to fetch data from API. The issue is I need to know what if I need to fetch data one time and call in all my pages?
This is the code in Document:
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Album> fetchAlbum() async {
final response =
await http.get(Uri.https('jsonplaceholder.typicode.com', 'albums/1'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
class Album {
final int userId;
final int id;
final String title;
Album({this.userId, this.id, this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
userId: json['userId'],
id: json['id'],
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<Album> futureAlbum;
@override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
@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<Album>(
future: futureAlbum,
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();
},
),
),
),
);
}
}
What I exactly need to know is in init state I am calling a function like this futureAlbum = fetchAlbum();
But I don't want to recall it again and again on the second page.
Assume this is the second page. How I can call data from fetchAlbum() function without calling api again ? I hope my question is understandable :D What is the main purpose is i don't want to hit my api again and again. I need to show data on my second page without api hit.
class SecondPage extends StatefulWidget {
@override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
@override
void initState() {
super.initState();
}
@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: ,
)
);
}
}
Upvotes: 2
Views: 2885
Reputation: 5575
Anytime you start running into issues accessing your data from different parts of the app, and controlling when you call an API, it's really time to invest in learning a state management solution to make this type of stuff much easier. There's also no need to have your fetchAlbum
be a top level global function.
Really, any state management would work. But here's a way using GetX State Management.
This will give you will have full control over how and when you call your API, and it won't rely on initState
of a stateful widget. Your fetchAlbum
function can live in its own class.
class AlbumController extends GetxController {
Album newAlbum;
Future<Album> fetchAlbum() async {
debugPrint('fetchAlbum called');
final response =
await http.get(Uri.https('jsonplaceholder.typicode.com', 'albums/1'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
newAlbum = Album.fromJson(jsonDecode(response.body));
debugPrint('id: ${newAlbum.id} title: ${newAlbum.title}');
return newAlbum;
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
}
All your widgets can now be stateless. As an example, here's how you could setup your MaterialApp
and a couple of pages. I added a couple buttons going back and forth between the pages and a print statement so you can see when the API is being called.
void main() {
Get.put(AlbumController()); // initializing the Getx Controller
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Page1(),
routes: {
Page1.id: (context) => Page1(),
Page2.id: (context) => Page2(),
},
);
}
}
class Page1 extends StatelessWidget {
static const id =
'page_1'; // this is so you don't have to use raw strings for routing
@override
Widget build(BuildContext context) {
final controller =
Get.find<AlbumController>(); // finding the initialized controller
return Scaffold(
appBar: AppBar(
title: Text('Fetch Data Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FutureBuilder<Album>(
future: controller
.fetchAlbum(), // this now lives it its own AlbumController class
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();
},
),
FlatButton(
child: Text('Go to page 2'),
color: Colors.blue,
onPressed: () {
Navigator.pushNamed(context, Page2.id);
},
)
],
),
),
);
}
}
class Page2 extends StatelessWidget {
static const id = 'page2';
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GetBuilder<AlbumController>( // this will update the data displayed when fetchAlbum() is called
builder: (controller) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('Id: ${controller.newAlbum.id}'),
Text('UserId: ${controller.newAlbum.userId}'),
Text('Title: ${controller.newAlbum.title}'),
FlatButton(
child: Text('Go to page 1'),
color: Colors.blue,
onPressed: () {
Navigator.pushNamed(context, Page1.id);
},
)
],
);
},
),
),
);
}
}
Now nothing is global, and the data is displayed on Page2
without calling the API again. It will call the API again when you go back to Page1
because of the FutureBuilder
, but you don't have to use a FutureBuilder
if you don't want that behavior. There are other ways to have better control of when the API is called.
Upvotes: 1
Reputation: 7308
If I have understood correctly you want to be able to call fetchAlbum
but if it has already made a call to the API it will return the data previously fetched.
The simplest way that comes in my mind would then be to keep an instance of the data you've got from your request.
Here is how I would do:
Album _savedAlbum;
Future<Album> fetchAlbum() async {
// Check if you don't have already fetched your album
if (_savedAlbum != null) return _savedAlbum;
final response =
await http.get(Uri.https('jsonplaceholder.typicode.com', 'albums/1'));
if (response.statusCode == 200) {
_savedAlbum = Album.fromJson(jsonDecode(response.body));
return _savedAlbum;
} else {
throw Exception('Failed to load album');
}
}
By adding this private variable _savedAlbum
you can ensure that if you have already fetched data from your API you will return it right before you made any other API call.
Upvotes: 0
Reputation: 1084
Pass you result as an attribut of your second page (must add to the Sateful and State class) :
class _SecondPageState extends State<SecondPage> {
// Attribut album
Album a;
// Add the constructor, this syntax is wrong but you get the idea
_SecondPageState({this.album})
...
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
// Use the object you passed in paramters
title: Text('${album.title}'),
),
body: ,
)
);
}
...
}
EDIT : Save the list of album you've already fetch in a map(id, album)
HashMap <int,Album> albumMap = new HashMap<int, Album>();
Future<Album> fetchAlbum(int id) async {
// Get the album from the map, not sure about the syntax to get a value from a hashmap using its key
Album album = albumMap[id];
// If the album is in the map, we return it, else we fetch it from the API and save it
if(album != null) {
return album;
} else {
final response =
await http.get(Uri.https('jsonplaceholder.typicode.com', 'albums/1'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
Album albumFromApi = Album.fromJson(jsonDecode(response.body));
// Save the album in the map, same not sure about the syntax.
albumMap[id] = albumFromApi;
return albumFromApi
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
}
Upvotes: 1