I have tried using FutureBuilder for a stateful widget,
I wrote a simple flask app and have checked the endpoint, it works well through the browser.
BUT in my flutter app I keep getting "no Data" which means no fetch from the server was done.
How come the function getFamily() is not getting invoked (see below)?
I guess i'm missing here something basic :/ , i'd like to get the family.father!
below is my code in the stateful widget:
Future<FamilyModel> familyFuture;
#override
void initState() {
super.initState();
familyFuture = getFamily();
}
Future<AuditionModel> getFamily() async {
print("Welcome to getFamily!!\n");
final url = "http://192.168.1.2:5000/search";
final response = await http.get(url);
// for debugging only
# print('statusCode: ${response.statusCode}');
if (response.statusCode == 200) {
# print("You have reached here!!!");
final jsonFamily = jsonDecode(response.body);
return FamilyModel.fromJson(jsonFamily);
} else {
throw Exception();
}
}
in the build part:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyCustomedAppBar(), // works well
body: Container(
child: FutureBuilder(
future: familyFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
switch (snapshot.connectionState) {
case ConnectionState.active:
return Text('Active');
case ConnectionState.done:
final family = snapshot.data;
print("Done!!!");
return Text(family.father);
case ConnectionState.none:
return Text("none");
case ConnectionState.waiting:
return Text("waiting");
default:
return Text("default");
}
} else {
return Text("No data");
}
},
),
),
);
The model is:
class FamilyModel {
final String father;
final String mother;
final String sister;
FamilyModel(
{this.father,
this.mother,
this.sister});
factory AuditionModel.fromJson(final json) {
return FamilyModel(
father: json["father"],
mother: json["mother"],
sister: json["sister"]);
}
Thank you all for the help!
The future attribute for the FutureBuilder should be a function. In this case your future should be getFamily(). Please refer to the docs on FutureBuilder here.
Related
I am trying to read a list of objects from Realtime database in Firebase with no success so far.
I am following the official tutorial from Firecasts on the following link:
https://www.youtube.com/watch?v=sXBJZD0fBa4
Here is how the database looks like:
Here is the code I have written:
The following is the class I have created for the objects I will be reading from database:
class TestData {
final String name;
final String surname;
final String age;
TestData({
required this.name,
required this.surname,
required this.age,
});
factory TestData.fromRTDB(Map<dynamic, dynamic> data) {
return TestData(
name: data["Name"],
surname: data["Surname"],
age: data["Age"],
);
}
}
Here is how I try to read it from the database:
class TestDataGetter {
final _db = FirebaseDatabase.instance.ref();
Stream<List<TestData>> getTestDataStream() {
final csPostprocessedStream = _db.child("test_data/").onValue;
var streamToPublish = csPostprocessedStream.map((event) {
final testDataMap = Map<dynamic, dynamic>.from(
event.snapshot.value as Map<dynamic, dynamic>);
final testDataList = testDataMap.entries.map((element) {
return TestData.fromRTDB(Map<dynamic, dynamic>.from(element.value));
}).toList();
return testDataList;
});
return streamToPublish;
}
}
And here is the screen where I would like to show the data:
class TestDataScreen extends StatelessWidget {
const TestDataScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
StreamBuilder(
stream: TestDataGetter().getTestDataStream(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
print("Waiting");
return const CircularProgressIndicator(color: Colors.white);
} else if (snapshot.hasError) {
print("Error occured...");
return const CircularProgressIndicator(color: Colors.white);
} else {
final testDataList = snapshot.data as List<TestData>;
return Text(
testDataList[0].name + " / " + testDataList[1].name,
style: Theme.of(context).textTheme.headline3,
textAlign: TextAlign.center);
}
}),
],
),
);
}
}
But I can never see the data on the screen. The spinning wheel is the only thing I see and on the console I see the print out as "Waiting" (as I print out this text in the code above).
It gets stuck in if (!snapshot.hasData).
I am clueless after spending hours on this.
Try the following
!snapshot.hasData || snapshot.data.documents.isEmpty
The problem here is that snapshots() will also return a QuerySnapshot when the query returns no documents. Thus, you could expand your condition like this:
Here is what I have should help you
if (snapshot.hasError) {
//TODO: we need to make sure we caputer this error message snapshot.error
return const PlaceHolder(
url: 'assets/animations/404-error.json',
message: ErrorMessages.getDogsError,
);
} else if (snapshot.data == null || snapshot.data!.isEmpty) {
return const PlaceHolder(
url: 'assets/animations/no-dog-animation.json',
message: AppMessages.noDogMessage,
);
} else if (!snapshot.hasData) {
}
Your problem is that you most likely have an error.
The issue is, your error handling is actually never reached, because in case of an error, !hasData will still be true
You may want to execute the hasError condition before !hasData:
if (snapshot.hasError) {
return Text('error');
} else if (!snapshot.hasData) {
return Text('loading');
} else {
return Text('data');
}
Currently my code works, but every 5 seconds I must make a request to the API, in order to refresh the view and see if any changes have occurred in the data.
My purpose is that it detect if there was any change in the data so that the view is updated, I am not using firebase and I am not trying to navigate to another screen either.
class _ListChatsState extends State<ListChats> {
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
final messagesProvider = new ServiceMessagesAdm();
Timer _timer;
#override
void initState() {
//messagesProvider.getMessagesAdm();
//Check the server every 5 seconds
_timer = Timer.periodic(Duration(seconds: 5), (timer) => messagesProvider.getMessagesAdm());
super.initState();
}
#override
void dispose() {
//cancel the timer
if (_timer.isActive) _timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
key: scaffoldKey,
appBar: new AppBar(
title: new Text('StreamBuilder'),
actions: <Widget>[
],
),
body: FutureBuilder(
future: messagesProvider.getMessagesAdm(),
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
print("ESTOY EN NONE");
return image();
break;
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
break;
case ConnectionState.active:
return _buildListView(snapshot.data);
break;
case ConnectionState.done:
// TODO: Handle this case.
break;
}
return image();
},
),
);
}
Future<List<MessagesAdm>> getMessagesAdm() async {
final respuesta = await http.get(url);
List<MessagesAdm> _list;
var resBody = json.decode(respuesta.body);
print(respuesta.body);
var capsules = resBody as List;
_list = capsules.map((model) => MessagesAdm.fromJson(model)).toList();
//print(_list);
popularesSink( _list );
return _list;
}
Is there a way to detect if there was any change within the API? I am chatting.
Before I was using these lines of code .. But they did not detect changes, for me it is important to tell you that the changes were being applied from postman. In the code, could I issue any notification that triggers those streams? I leave lines of the previous code used.
class ServiceMessagesAdm {
Loads loads;
final _popularesStreamController = StreamController<List<MessagesAdm>>.broadcast();
Function(List<MessagesAdm>) get popularesSink => _popularesStreamController.sink.add;
Stream<List<MessagesAdm>> get popularesStream => _popularesStreamController.stream;
void disposeStreams() {
_popularesStreamController?.close();
}
}
Am trying to get the distance two coordinate in a future build which was successful but am looking for how to get the value returned "kmDis" in Text() widget in body: of my code. Check below for my full code.
Future<String> getDistance(String lat, String lng) async {
final distanceInMeters = await Geolocator().distanceBetween(
currentLocation.latitude,
currentLocation.longitude,
double.parse(lat),
double.parse(lng));
double distancekm = distanceInMeters / 1000;
String kmDis = distancekm.toStringAsFixed(1);
//int finalkm = distancekm.round();
return kmDis;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body:Text("i want to return my kmDis value as string here.")
)
}
Well, you are dealing with a function that return a Future.
So, you can use a FutureBuilder to operate with this function and respond to different states.
This is a simple code, which deals with a similar situation.
The someFutureStringFunction() is your getDistance().
Also look into the FutureBuilder widget inside
the Scaffold()
Future<String> someFutureStringFunction() async {
return Future.delayed(const Duration(seconds: 1), () => "someText");
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: someFutureStringFunction(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data);
} else {
return Text('Loading...');
}
},
),
);
}
you can create two methods, one to extract data which you put in a variable and another as like getter (In my case I need to access secure value with plugin flutter_secure_storage without using Futurebuilder)
mixin SecureStorageMethods {
String keyValue;
Future<String> _getFromSecureStorage(String key) async {
keyValue = await slL<FlutterSecureStorage>().read(key: key);
return keyValue;
}
String getSecureValue(String key) {
_getFromSecureStorage(key);
return keyValue;
}
}
So I call in my code
Text(
getSecureValue(YOUR_KEY),
style: OurStyle.textFieldStyle,
)
I'm doing an Flutter application, but I found a problem when I wanted to make some http request. The problem is that when I want to make it, I define the function as asynchronous, and I write await before calling the function http.get(), but the function it´s not executed and the code after the function is not executed also.
The code is below and no error is thrown.
class db{
void get_basic() async{
String url = 'http://example.org/';
Response response = await get(url);
int statusCode = response.statusCode;
print("Listo");
print(statusCode);
}
}
Widget build(BuildContext context){
print("inicio database");
db database = db();
database.get_basic();
print("final database");
main_content main = main_content();
return Scaffold(
appBar: AppBar(
title: Text('Title),
),
body: main,
bottomNavigationBar: bottomNavBar(0,main.refresh),
);
}
async functions always return a future.
A Future object represents a computation whose return value might not yet be available. The Future returns the value of the computation when it completes at some time in the future. Futures are often used for potentially lengthy computations such as I/O and interaction with users.
async functions return futures, that means that they are performed in the future.
because dart uses a single thread to run code that means when it hits await instead of blocking the thread it will move to after the function call and starts exciting code again until it idles.
when the thread has done all of the synchronous code it will go back to await
line and start exciting there.
The get_basic() code should be called after the build is complete.
if you want to rebuild your widget after get_basic() is completed you need to use a FutureBuilder:
class _MyHomePageState extends State<MyHomePage> {
db database;
#override
void initState() {
super.initState();
database = db();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Title'),
),
body: FutureBuilder(
future: database.get_basic(),
builder: (context,snapshot){
if(snapshot.connectionState!=ConnectionState.done){
return Center(
child: Text('Loading...'),
);
} else {
if(snapshot.hasError){
return Center(
child: Text(snapshot.error.toString()),
);
}
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index){
return ListTile(title: Text(snapshot.data[index]['title']),);
},
);
}
},
),
);
}
}
class db{
Future get_basic() async{
try {
String url = 'https://jsonplaceholder.typicode.com/posts';
Response response = await get(url);
return jsonDecode(response.body);
} catch (e) {
print(e);
return [{}];
}
}
}
I am try load Firestore snapshots() into StreamController so can give to StreamBuilder so can build newsfeed in app.
But get error:
The getter 'stream' was called on null.
The method 'add' was called on
null.
Here my code:
StreamController<QuerySnapshot> _localStreamController = StreamController<QuerySnapshot>();
#override
void initState() {
super.initState();
Firestore.instance.collection(‘info’).snapshots().listen((QuerySnapshot querySnapshot) {
// if(userAdded == null) {
_localStreamController.add(querySnapshot);
// }
});
...
child: StreamBuilder(
stream: _localStreamController.stream,
builder: (context, snapshot) {
Anyone know solution?
Thanks!
Solution #1 :
You need to initialize the stream Replace this line
StreamController<QuerySnapshot> _localStreamController = StreamController<QuerySnapshot>();
with:
StreamController<QuerySnapshot> _localStreamController = StreamController.broadcast();
because broadcast() need to listen to data before requesting it from firebase you need to add listener in the initSatat it will be some thing like this :
#override
void initState() {
// this doesn't have to do any thing
_localStreamController.stream.listen((event)=>print(event));
Firestore.instance.collection('info').snapshots().listen((QuerySnapshot querySnapshot) =>
_localStreamController.add(querySnapshot));
super.initState();
}
and your stream builder look like this :
StreamBuilder(
stream: _localStreamController.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ...
} else {
return CircularProgressIndicator();
}
},
)
Solution #2 :
you can loss the controller and stream the data directly in the Stream builder all throw it's not best practice.
it will be some thing like this:
StreamBuilder(
stream: Firestore.instance.collection('info').snapshots(), // <= change
builder: (context, snapshot) {
if (snapshot.hasData) {
return ...
} else {
return CircularProgressIndicator();
}
},
)
and you don't to use StreamController or initState
You need to initialize the stream
Replace this line:
StreamController<QuerySnapshot> _localStreamController = StreamController<QuerySnapshot>();
with:
StreamController<QuerySnapshot> _localStreamController = StreamController.broadcast();
Then in your builder, you will want to account for the data not being loaded yet. So showing a loading screen or something could be useful. Something like this:
if (!snapshot.hasData || snapshot.data.documents.length == 0) {
return Center(child: const Text('Loading ...'));
}