i would like to know whats the diffrence between using setState and snapshot when fetching apis for example
the way i fetch the apis is like the following
Widget text = Container;
Future<AnyClass> fetch() async{
final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
var result = AnyClass.fromJson(jsonDecode(response.body));
setState(()=> text = result.title)
}
#override
Widget build(BuildContext context) {
return Contianer(child:text)
}
there is another way which uses the snapshot to featch the data instead of using state like the following
Future<Album> fetchAlbum() async { final response = await http
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
return Album.fromJson(jsonDecode(response.body));
} #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: const 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 const CircularProgressIndicator();
},
),
),
),
); }
so i would like to know what are the diffrence between these two methods. Thanks
None, you can check the FutureBuilder source code to see that is does exactly the same: call setState when it has a response.
The advantage of FutureBuilder are:
Easy handling of the different state (loading, loaded, failure)
You don't have to create a StatefulWidget, which means less line of code
Related
I have just created a demo for better understanding future builder
scaffold body showing all users from api and appear should be shown with number of users
appear's title showing 0 when loaded but does not change...what to do to rebuild it
here is my code
class _withmodelState extends State<withmodel> {
List<UserModel> userlist=[];
Future<List<UserModel>> getdata() async {
final resp =
await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
if (resp.statusCode == 200) {
print('i ma called');
List<dynamic> dlist = json.decode(resp.body);
await Future.delayed(Duration(seconds: 2));
userlist= dlist.map((e) => UserModel.fromJson(e)).toList();
return userlist;
}
return userlist;
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(title: Text("Total users="+userlist.length.toString()),),
body: MyBody(
//MyBody returning FutureBuilder for showing userlist array;
),
));
}
You can use ChangeNotifier like this, first create a class like this:
class WithmodelDecl with ChangeNotifier {
ValueNotifier<int> totalUsers = ValueNotifier<int>(0);
}
WithmodelDecl withmodeldecl = new WithmodelDecl();
then use it like this:
return SafeArea(
child: Scaffold(
appBar: PreferredSize(
child: ValueListenableBuilder<int>(
valueListenable: withmodeldecl.totalUsers,
builder: (context, value, _) {
return AppBar(
title: Text("Total users=" + value.toString()),
);
}),
preferredSize: AppBar().preferredSize),
body: MyBody(
//MyBody returning FutureBuilder for showing userlist array;
),
));
and finally change your getdata to this:
Future<List<UserModel>> getdata() async {
final resp =
await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
if (resp.statusCode == 200) {
print('i ma called');
List<dynamic> dlist = json.decode(resp.body);
await Future.delayed(Duration(seconds: 2));
userlist= dlist.map((e) => UserModel.fromJson(e)).toList();
withmodeldecl.totalUsers.value = userlist.length;
return userlist;
}
return userlist;
}
You also need to rebuild the Text widget, that you are using to show the count, when the count is available, i.e., the Future completes.
You need to wrap that Text widget with FutureBuilder like this:
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: FutureBuilder<List<UserModel>>(
future: getdata(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
final List<UserModel> userlist = snapshot.data!;
return Text("Total users= ${userlist.length}");
// it's better to use String interpolation than "Total users=" + snapshot.data!.length.toString()
} else {
// return loading widget
}
},
),
),
body: MyBody(
//MyBody returning FutureBuilder for showing userlist array;
),
),
);
It is better to have the Future in a variable, and then use it like this, to avoid unwanted and repeated calling of it whenever the build() method is called:
late final Future<List<UserModel>> _userListFuture;
And initialize it in your initState(), like this:
#override
void initState() {
super.initState();
_userListFuture = Future<List<UserModel>>(getdata);
}
And use it with your FutureBuilder like this:
FutureBuilder<List<UserModel>>(
future: _userListFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// return your widget showing data
} else {
// return loading widget
}
},
)
Whenever I try fetching data from a REST API, I keep getting an error "Expected a value of type 'Widget?', but got one of type 'String'". There is nothing wrong with my code yet I keep getting the error.
This is the function for fetching items from the database.
Future<List<Map>> fetchItems() async {
List<Map> items = [];
//get data from API and assign to variable
http.Response response =
await http.get(Uri.parse("https://jsonplaceholder.typicode.com/posts"));
if (response.statusCode == 200) {
//get data from the response
String jsonString = response.body;
items = jsonDecode(jsonString).cast<Map>();
}
return items;
}
This is my main.dart file
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: PostList(),
);
}
}
class PostList extends StatelessWidget {
PostList({super.key});
final Future<List<Map>> _futurePosts = HTTPHelper().fetchItems();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Posts"),
),
body: FutureBuilder(
future: _futurePosts,
builder: ((context, snapshot) {
//check for error
if (snapshot.hasError) {
return Center(
child: Text("Some error has occured ${snapshot.error}"));
}
//has data
if (snapshot.hasData) {
List<Map> _posts = snapshot.data!;
return ListView.builder(
itemCount: _posts.length,
itemBuilder: ((context, index) {
Map _thisItem = _posts[index];
return ListTile(
title: _thisItem["title"],
subtitle: _thisItem["body"],
);
}));
}
//display a loader
return Center(child: CircularProgressIndicator());
}),
),
);
}
}
Any solution to this error?
The answer is pretty simple. You're assigning directly string value to the title(Which is expecting Widget).
You can try below code
ListView.builder(
itemCount: _posts.length,
itemBuilder: ((context, index) {
Map _thisItem = _posts[index];
return ListTile(
title: Text(_thisItem["title"].toString()),
subtitle: Text(_thisItem["body"].toString()),
);
}));
If this doesn't work. Please let me know.
ListTile(
title:NEED WIDGET HERE,
subtitle:NEED WIDGET HERE,)
The API im using is a car registry where you type in a numberplate and it will show details about the vehicle. Basically i have tried implementing a Textfield where i can retrieve it's value so i can insert in the API url (VALUE FROM TEXTFIELD). I have have tried implementing it using this tutorial: https://docs.flutter.dev/cookbook/forms/retrieve-input but with no luck.
Future<Album> fetchAlbum() async {
final response = await http
.get(Uri.parse('https://v1.motorapi.dk/vehicles/(VALUE FROM TEXTFIELD)'),
headers: {"X-AUTH-TOKEN": "rfrzsucnc7eo3m5hcmq6ljdzda1lz793",
"Content-Type": "application/json",
"Accept": "application/json",
});
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');
}
}
I have tried to inserting Textfield in the widget but that didn't work
class _MyAppState extends State<MyApp> {
late Future<Album> futureAlbum;
#override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
TextEditingController nummerpladeController = new TextEditingController();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text('Fetch Data Example'),
),
body: Center(
child:
FutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column( mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ Text("Registreringsnummer: ""${snapshot.data!.registration_number}"),
Text("Status: ""${snapshot.data!.status}"),
Text("Type: ""${snapshot.data!.type}"),
Text("Brug: ""${snapshot.data!.use}"),
Text("Første registrerings dato: ""${snapshot.data!.first_registration}"),
Text("Vin nummer: ""${snapshot.data!.vin}"),
Text("Mærke: ""${snapshot.data!.make}"),
Text("Model: ""${snapshot.data!.model}"),
Text("Variant: ""${snapshot.data!.variant}"),
Text("Model type: ""${snapshot.data!.model_type}"),
Text("Farve: ""${snapshot.data!.color}"),
Text("Bil type: ""${snapshot.data!.chasis_type}"),
Text("Brændstof: ""${snapshot.data!.fuel_type}"),
Text("Sidste syn: ""${snapshot.data!.date}"),
], );
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return const CircularProgressIndicator();
},
),
),
),
);
}
}
You have to assign the TextEditingController to a TextField widget like so:
TextField(
controller: nummerpladeController,
);
To retrieve its value you have to call nummerpladeController.text
Furthermore, you currently are not able to access your nummerpladeController in your build method because it is defined in the initState method. To fix this, do the following:
late final TextEditingController nummerpladeController;
#override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
nummerpladeController = new TextEditingController();
}
You can read more about what late is here: https://dart.dev/null-safety/understanding-null-safety#lazy-initialization
My problem is that I don't know how to pass the argument of a route to my function which is inside the FutureBuilder.
Please see my code below.
class StudyDetailsArguments {
final String listid;
StudyDetailsArguments(this.listid);
}
// A widget that extracts the necessary arguments from the ModalRoute.
class ExtractStudyDetails extends StatelessWidget {
static const routeName = '/studydetails';
#override
Widget build(BuildContext context) => FutureBuilder(
// Here I want to pass in the args.listid but I cant figure out how to get it in there
future: getDetails("cc0e5c1f-02b0-4f4f-9f51-fa70ac7e9c08"),
builder: (context, snapshot) {
// here is fine to use the argument
final StudyDetailsArguments args = ModalRoute.of(context).settings.arguments;
if (snapshot.hasData) {
// Build the widget with data.
//return Text('hasData: ${snapshot.data}');
return Scaffold(
appBar: AppBar(
title: Text(snapshot.data),
),
body:
Center(
child:
Text('${snapshot.data}'))
);
} else {
// We can show the loading view until the data comes back.
return Scaffold(
appBar: AppBar(
title: Text("Loading..."),
),
body:
Center(
child:
Text('Loading...'))
);
}
},
);
}
Future<String> getDetails(listid) async {
var details = "";
await Firestore.instance
.collection('list')
.document(listid)
.get()
.then((DocumentSnapshot ds) {
print(ds.data["title"]);
// use ds as a snapshot
details = ds.data["title"];
return ds.data;
});
return details;
}
I want to use this line args.listid instead of cc0e5c1f-02b0-4f4f-9f51-fa70ac7e9c08 but I can't seem to figure out the way to pass the parameter. How I send the parameter value (which now is not used) to the widget is this way in a widget:
onTap: () => {
Navigator.pushNamed(context, ExtractStudyDetails.routeName,
arguments: StudyDetailsArguments(
study.listid,
))
},
#override
Widget build(BuildContext context) {
final StudyDetailsArguments args = ModalRoute.of(context).settings.arguments;
return FutureBuilder(
future: getDetails(args.listid),
[...]
)
}
Read more on docs
I am following old tutorial, and I get an error on ImageUrl in last line of code:
"Future dynamic can't be assigned to argument type String".
How can I fix that?
class _MyAppState extends State<MyApp> {
static FirebaseStorage storage = FirebaseStorage(
storageBucket: 'gs://natapp-7d2db/storage/natapp-7d2db.appspot.com/files'
);
static StorageReference imageRef = storage.ref().child('cake.png');
final imageUrl = imageRef.getDownloadURL();
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(brightness: Brightness.dark),
home: Container(
child: Image.network(imageUrl),
),
);
}
}
EDIT: I used
child: Image.network(imageUrl.toString)
Replace Container with Use FutureBuilder, as FutureBuilder is a widget which used for an async callback which runs on Future
FutureBuilder<String>(
future: imageRef.getDownloadURL(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Container(
child: Image.network(snapshot.data));
}
if (snapshot.hasError) return WidgetThatShowsError();
// by default show progress because operation is async and we need to wait for result
return CircularProgressIndicator();
},
);