I'm new and currently learning Flutter, I have a question about FutureBuilder. My code is stuck at ConnectionState.waiting and I'm not sure why.
My codes are
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class DartPad extends StatefulWidget {
const DartPad({Key? key}) : super(key: key);
#override
State<DartPad> createState() => _DartPadState();
}
class _DartPadState extends State<DartPad> {
List three = [];
Future<dynamic> fetchData() async {
var sheetID = '1j7sYExCP0etGw_LoxqYTFRrjwHmJv73SHzC26jbtpH4';
var sheetTab = 'daftar';
var url = Uri.parse('https://opensheet.elk.sh/$sheetID/$sheetTab');
http.Response response;
response = await http.get(url);
setState(() {
three = json.decode(response.body);
});
}
#override
void initState() {
fetchData();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
centerTitle: true,
title: const Text('Dartpad'),
),
body: FutureBuilder<dynamic>(
future: fetchData(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
return Text("${three[1]["nama"] ?? ''}");
}
}
},
),
);
}
}
I have tried to delete
case ConnectionState.waiting:
return Text('Loading....');
But received an error.
I have also tried to changed .hasData on snapshot (suggested from other thread) but the editor did not accept it.
I tried to not use switch and direcly used if but received RangeError (RangeError (index): Invalid value: Valid value range is empty: 1) error.
I want to run three[1]["nama"] (preferably in loop so I can display the rest of the data)
Thank you!
The reason this happened is that you are not return anything in fetchData, change fetchData to this:
Future<dynamic> fetchData() async {
var sheetID = '1j7sYExCP0etGw_LoxqYTFRrjwHmJv73SHzC26jbtpH4';
var sheetTab = 'daftar';
var url = Uri.parse('https://opensheet.elk.sh/$sheetID/$sheetTab');
http.Response response;
response = await http.get(url);
return json.decode(response.body);
}
then define new variable like this:
Future fetchFuture;
then use it like this inside initState:
#override
void initState() {
super.initState();
fetchFuture = fetchData();
}
then change your last return in your builder to this:
FutureBuilder<dynamic>(
future: fetchFuture,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
List data = snapshot.data ?? [];
return Text("${data[1]["nama"] ?? ''}");
}
}
},
)
There is nothing wrong with your code, it is your own mistake
Text("${three[1]["nama"] ?? ''}")
// Does your List really have two values?
// Is the Key value really 'nama'? Are you sure it's not a name?
Related
I'm trying to create a ListView with some data received from Firebase, but I keep getting this message. I have tried with FutureBuilder but nothing was useful
The argument type 'Future' can't be assigned to the parameter type 'Widget?'
Code is here:
class CityScreen extends StatelessWidget {
const CityScreen({Key? key}) : super(key: key);
Future<ListView> CreateList() async {
List<CityButton>? listButtons;
final FirebaseAuth auth = FirebaseAuth.instance;
final User? user = auth.currentUser;
final uid = user!.uid;
CollectionReference trips = FirebaseFirestore.instance.collection('events');
QuerySnapshot eventsQuery =
await trips.where("uid", isEqualTo: uid).orderBy('initDate').get();
// ignore: avoid_function_literals_in_foreach_calls
eventsQuery.docs.forEach((element) {
listButtons!.add(CityButton(
element['city'],
DateTime.parse(element['initDate']),
DateTime.parse(element['endDate'])));
});
return ListView(
children: listButtons!,
);
}
#override
Widget build(BuildContext context) {
Future<ListView> lista = CreateList();
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
body: lista,
CreateList() method is a future, use FutureBuilder
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
body: FutureBuilder<ListView>(
future: CreateList(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
return snapshot.data!;
} else {
/// you handle others state like error while it will a widget no matter what, you can skip it
return const CircularProgressIndicator();
}
},
),
When you want to render widget after async then use FutureBuilder()
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<ListView>(
future: CreateList(), // async work
builder: (BuildContext context, AsyncSnapshot<ListView> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading....');
default:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
else
return snapshot.data;
}
},
),
);
}
I am getting a connection state as none when trying to get the data from locally saved JSON.
what I trying to do is build the dropdown widget with the JSON as dropdown values
class _TestState extends State<Test> {
var stateList;
initState() {
_getStateList();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: FutureBuilder(
future: _getStateList(), // async work
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
print(snapshot);
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('Press button to start.');
case ConnectionState.active:
case ConnectionState.waiting:
return Text('Awaiting result...');
case ConnectionState.done:
if (snapshot.hasError) return Text('Error: ${snapshot.error}');
return Text('Result: ${snapshot.data}');
// You can reach your snapshot.data['url'] in here
}
},
)),
);
}
_getStateList() {
// setState(() {
DetailServices().stateJson().then((res) => {stateList = res});
// });
}
}
You need a Future<String> for the future property. So, change the following code:
_getStateList() {
// setState(() {
DetailServices().stateJson().then((res) => {stateList = res});
// });
}
to:
Future<String> _getStateList() {
return DetailServices().stateJson();
}
the issue is _getStateList doesn't return anything it should either return a future or you create a field that is the future and set that in _getStateList
I have an old article on medium that's about Asynchronously changing the UI which might help you
Article Link
i have working with test app, its just display list of employees from api call, for that i have created data model for employee and calling it. but i get nothing i know somewhere it goes wrong help me to find out the problem(actually no errors but, its does not load the data).
here is the snippets
import 'package:flutter/material.dart';
import '../models/employee.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class EmployeeListScreen extends StatefulWidget {
EmployeeListScreen({Key key}) : super(key: key);
#override
_EmployeeListScreenState createState() => _EmployeeListScreenState();
}
class _EmployeeListScreenState extends State<EmployeeListScreen> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Employee List"),
),
body: FutureBuilder(
future: fetchEmployees(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.none) {
return Center(
child: Text("None"),
);
}
if (snapshot.connectionState == ConnectionState.active) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return Center(child: Text("No Employees"));
} else {
return Center(
child: ListView.builder(
itemCount: snapshot.data.length[![enter image description here][1]][1],
itemBuilder: (BuildContext context, int index) {
return Text(snapshot.data[index]["name"]);
},
),
);
}
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
return Container();
},
));
}
Future<List<Employee>> fetchEmployees() async {
final response = await http.get(
"http://192.168.1.199/projects/ci/employee/api/getEmployees",
headers: {"accept": "application/json"});
debugPrint("Api Finished...");
if (response.statusCode == 200) {
final result = jsonDecode(response.body);
Iterable list = result['employees'];
print(list);
return list.map((employee) => Employee.fromJson(employee)).toList();
} else {
throw Exception("Failed to Load Employees");
}
}
}
see the screen shots.
i have the result while am using traditional api call without using model and factory methods, its very confusing to me also suggest me for best sites to learn these things, even i saw the official document it not clear at all.
To help debug the issue, how about trying this simplified code below. Call your fetchEmployees() from inside loadSlowData() method.
(It's not good practice to make an async call directly in FutureBuilder future:. Instead, make the async call in initState of the StatefulWidget. Since FutureBuilder is inside the build() method, and build could be called up to 60 times a second, you can obviously see the problem. If you happen to use an animation on that part of the widget tree, which refresh at 60fps, you'll get that situation.)
import 'package:flutter/material.dart';
class FutureBuilderStatefulPage extends StatefulWidget {
#override
_FutureBuilderStatefulPageState createState() => _FutureBuilderStatefulPageState();
}
class _FutureBuilderStatefulPageState extends State<FutureBuilderStatefulPage> {
Future<String> _slowData;
#override
void initState() {
super.initState();
_slowData = loadSlowData();
}
Future<String> loadSlowData() async {
// replace with your async call ↓ ↓
return await Future.delayed(Duration(seconds: 2), () => 'The Future has arrived');
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('FutureBldr Stateful'),
),
body: FutureBuilder<String>(
future: _slowData,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Center(child: Text(snapshot.data));
}
return Center(child: Text('Loading...'));
},
),
);
}
}
You can possibly Try snapShot.hasData instead of snapshot.data
I am trying to fetch a quote from the an api https://type.fit/api/quotes but it's not showing and its show me the this error: type 'List<dynamic>' is not a subtype of type 'Map<String, dynamic>'
This is the model class for the json:
class Autogenerated {
String text;
String author;
Autogenerated({this.text, this.author});
factory Autogenerated.fromJson(Map<String, dynamic> json) {
return Autogenerated(
text: json['text'],
author: json['author'],
);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['text'] = this.text;
data['author'] = this.author;
return data;
}
}
Now this I use the called this import 'package:http/http.dart' as http;
and now I used the http.get to call the api like this:
final response =
await http.get('https://type.fit/api/quotes');
here is the full code of it...
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:quotes_season/modal.dart';
import 'package:http/http.dart' as http;
class Quote extends StatefulWidget {
#override
_QuoteState createState() => _QuoteState();
}
Future<Autogenerated> fetchAlbum() async {
final response =
await http.get('https://type.fit/api/quotes');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Autogenerated.fromJson(json.decode(response.body));
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
class _QuoteState extends State<Quote> {
Future<Autogenerated> futureAutogenerated;
#override
void initState() {
super.initState();
futureAutogenerated = fetchAlbum();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: fetchAlbum(),
builder: (context, snapshot) {
if(snapshot.hasData){
return Center(child: Text(snapshot.data.title));
}else if(snapshot.hasError){
return Center(child: Text("${snapshot.error}"));
}
return CircularProgressIndicator();
}),
);
}
}
The site that you posted returns a List of what you modelled in your code as Autogenerated. Based on the rest of your code it seems you only want one of these Autogenerated objects, so you can just say to use the first index in the List that you retrieve.
Future<Autogenerated> fetchAlbum() async {
final response =
await http.get('https://type.fit/api/quotes');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
List parsedJson = json.decode(response.body);
return parsedJson.isNotEmpty ? Autogenerated.fromJson(parsedJson[0]) : null;
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
Alternatively, if you want you want to display all of quotes you can parse that and return a list of Autogenerated, but this would involve changing more code in displaying all of the quotes.
Future<List<Autogenerated>> fetchAlbum() async {
final response =
await http.get('https://type.fit/api/quotes');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
List jsonList = json.decode(response.body);
List list = jsonList.map((elem) => Autogenerated.fromJson(elem)).toList();
return list;
}
...
}
class _QuoteState extends State<Quote> {
Future<List<Autogenerated>> futureAutogenerated;
#override
void initState() {
super.initState();
futureAutogenerated = fetchAlbum();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: futureAutogenerated,
builder: (context, snapshot) {
if(snapshot.hasData) {
return ListView.builder(itemCount: snapshot.data.length, itemBuilder: (context, index) => Text("${snapshot.data[index].text}, ${snapshot.data[index].author}"));
}else if(snapshot.hasError) {
return Center(child: Text("${snapshot.error}"));
}
return CircularProgressIndicator();
}),
);
}
}
Full Working test code example - this should be used only as a proof of concept, you will need to implement this into your existing code:
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(MaterialApp(home:Quote()));
}
class Quote extends StatefulWidget {
#override
_QuoteState createState() => _QuoteState();
}
Future<List<Autogenerated>> fetchAlbum() async {
final response =
await http.get('https://type.fit/api/quotes');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
List jsonList = json.decode(response.body);
List list = jsonList.map((elem) => Autogenerated.fromJson(elem)).toList();
return list;
}
else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
class _QuoteState extends State<Quote> {
Future<List<Autogenerated>> futureAutogenerated;
#override
void initState() {
super.initState();
futureAutogenerated = fetchAlbum();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: futureAutogenerated,
builder: (context, snapshot) {
if(snapshot.hasData) {
return ListView.builder(itemCount: snapshot.data.length, itemBuilder: (context, index) => Text("${snapshot.data[index].text}, ${snapshot.data[index].author}"));
}else if(snapshot.hasError) {
return Center(child: Text("${snapshot.error}"));
}
return CircularProgressIndicator();
}),
);
}
}
class Autogenerated {
String text;
String author;
Autogenerated({this.text, this.author});
factory Autogenerated.fromJson(Map<String, dynamic> json) {
return Autogenerated(
text: json['text'],
author: json['author'],
);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['text'] = this.text;
data['author'] = this.author;
return data;
}
}
You are trying to fetch JSON list from the API endpoint, but your parsing code is parsing single JSON object.
Your method has to change to return list of objects:
Future<List<Autogenerated>> fetchAlbum() async {
final response =
await http.get('https://type.fit/api/quotes');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
List jsonList = json.decode(response.body)
List list = List.generate(jsonList.length, (i) => Autogenerated.fromJson(jsonList[i]));
return list;
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
After this change of the API call, your state class should look like this:
class _QuoteState extends State<Quote> {
Future<List<Autogenerated>> futureAutogenerated;
#override
void initState() {
super.initState();
futureAutogenerated = fetchAlbum();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: futureAutogenerated,
builder: (context, snapshot) {
if(snapshot.hasData){
List<Autogenerated> list = snapshot.data;
Autogenerated firstItem = list[0];
return Center(child: Text(firstItem.text));
}else if(snapshot.hasError){
return Center(child: Text("${snapshot.error}"));
}
return CircularProgressIndicator();
}),
);
}
}
If your goal is to create a list of elements, and not a single element, you would need to modify widget being used:
return Center(child: Text(firstItem.text));
and do something like this instead:
List<Autogenerated> list = snapshot.data;
return ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) => Text(list[index]),
);
I am trying to call the user first name in my drawer.
I already follow the suggestions in other question to use connection state, I use it already, but the result still null. The result when I try to call my drawer is 'Oops, there is no data'
Can someone show me which part is not correct? Really appreciate your help.
DatabaseService:
class DatabaseService {
final String uid;
final String taskId;
DatabaseService({this.uid, this.taskId});
//collection reference
CollectionReference userCollection = Firestore.instance.collection('user');
//get user data stream
Stream<UserData> get userData {
return userCollection.document(uid).snapshots().map(_userDataFromSnapshot);
}
//user data from snapshot
UserData _userDataFromSnapshot(DocumentSnapshot snapshot) {
return UserData(fName: snapshot.data['first_name']);
}
} // DatabaseService
Drawer:
class DrawerWidget extends StatelessWidget {
final _auth = AuthService();
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
return StreamBuilder<UserData>(
stream: DatabaseService(uid: user.uid).userData,
builder: (context, snapshot) {
UserData userData = snapshot.data;
if (snapshot.hasData) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Drawer(...);
default:
return Drawer(
child: Center(child: Text(userData.fName)), //Try to call the data here
);
}
} else {
return Drawer(
child: Center(child: Text('Oops, there is no data')),
);
}
});
}
}
Data in Firebase: