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 [{}];
}
}
}
Related
I want to get a string from my DB in Firebase, I'm very confused and I don't know how to do that!
I made a big search in the few past days about this idea but unf I don't get any useful result
what do I want? I want to make a Method that returns the 'Question' string.
DB:Collection / History/question
thank you for your time
the incorrect code :
Future loadData() async {
await Firebase.initializeApp();
if (snapshot.hasError) {
return Scaffold(
body: Center(
child: Text("Error: ${snapshot.error}"),
),
);
}
// Collection Data ready to display
if (snapshot.connectionState == ConnectionState.done) {
// Display the data inside a list view
return snapshot.data.docs.map(
(document) {
return method(
document.data()['question'].toString().toString(),
); //Center(
},
);
}
}
Here is the official documentation from Flutter Fire - https://firebase.flutter.dev/docs/firestore/usage/
Read data from Cloud firestore
Cloud Firestore gives you the ability to read the value of a collection or a document. This can be a one-time read or provided by real-time updates when the data within a query changes.
One-time Read
To read a collection or document once, call the Query.get or DocumentReference.get methods. In the below example a FutureBuilder is used to help manage the state of the request:
class GetUserName extends StatelessWidget {
final String documentId;
GetUserName(this.documentId);
#override
Widget build(BuildContext context) {
CollectionReference users = FirebaseFirestore.instance.collection('users');
return FutureBuilder<DocumentSnapshot>(
future: users.doc(documentId).get(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data.exists) {
return Text("Document does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data = snapshot.data.data();
return Text("Full Name: ${data['full_name']} ${data['last_name']}");
}
return Text("loading");
},
);
}
}
To learn more about reading data whilst offline, view the Access Data Offline documentation.
Realtime changes
FlutterFire provides support for dealing with real-time changes to collections and documents. A new event is provided on the initial request, and any subsequent changes to collection/document whenever a change occurs (modification, deleted, or added).
Both the CollectionReference & DocumentReference provide a snapshots() method which returns a Stream:
Stream collectionStream = FirebaseFirestore.instance.collection('users').snapshots();
Stream documentStream = FirebaseFirestore.instance.collection('users').doc('ABC123').snapshots();
Once returned, you can subscribe to updates via the listen() method. The below example uses a StreamBuilder which helps automatically manage the streams state and disposal of the stream when it's no longer used within your app:
class UserInformation extends StatefulWidget {
#override
_UserInformationState createState() => _UserInformationState();
}
class _UserInformationState extends State<UserInformation> {
final Stream<QuerySnapshot> _usersStream = FirebaseFirestore.instance.collection('users').snapshots();
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _usersStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return new ListView(
children: snapshot.data.docs.map((DocumentSnapshot document) {
return new ListTile(
title: new Text(document.data()['full_name']),
subtitle: new Text(document.data()['company']),
);
}).toList(),
);
},
);
}
}
By default, listeners do not update if there is a change that only affects the metadata. If you want to receive events when the document or query metadata changes, you can pass includeMetadataChanges to the snapshots method:
FirebaseFirestore.instance
.collection('users')
.snapshots(includeMetadataChanges: true)
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.
I have stumbled upon a weird problem.
The following function gets a Firestore document and returns it so other functions can access it's data
Future getCountRequests() async {
try{
return await _countReference.document('Requests').get();
} catch(e){
print(e.toString());
}
}
And this is the function in question that uses it's data.
int _countRequest() {
int toReturn;
CounterService().getCountRequests().then(
(doc) {
//print('Item in question: $doc, information from document ${doc.data}');
//this line prints correctly Instance of DocumentReference and {"amount" : 6}
toReturn = doc.data['amount'];
}
);
return toReturn;
}
When I run the code, I get an error message on my screen which states that the AnimatedList I am using receives null from the _countRequest() function.
Putting a break on this line has helped me understand that this block gets skipped completely
CounterService().getCountRequests().then( ...
However when I put a break on this line, it shows that the code inside the block works and that the document is indeed received through the getCountRequests() function.
toReturn = doc.data['amount'];
My question is, what causes the .then block to be skipped causing the function to return null?
What you are trying to do cannot work.
You are trying to return the result of an asynchronous computation from a synchronous function. There is no way to make that work.
An asynchronous computation will always complete later, and a synchronous function always returns now, so the result of the computation cannot be returned now.
The then call is what makes the operation asynchronous. It passes a callback to a future, but the only thing you are guaranteed about when that callback is called is that it is definitely not immediately. The then call returns immediately, returning a future which will be completed when the callback has been called, and your code returns the current value of toReturn, which is still null.
In the end I figured that doing this asynchronously with a FutureBuilder wouldn't work at all, or with my current skillset I just can't figure out how to make it work. So I decided to convert to a StreamBuilder which uses a Stream<QuerySnapshot> gotten from the following function.
Stream<QuerySnapshot> findAllRequests() {
try{
return accountRequestCollection.snapshots();
} catch(e){
print(e.toString());
return null;
}
}
and built the AnimatedList into the StreamBuilder like so.
class Request extends StatefulWidget {
#override
_RequestState createState() => _RequestState();
}
class _RequestState extends State<Request> {
final AdministrationService _administrationService = AdministrationService();
final GlobalKey<AnimatedListState> _globalKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Account Requests'),
),
body: StreamBuilder(
stream: _administrationService.findAllRequests(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting: return new Loading();
default: if (snapshot.hasError) {
return new Text('Error: ${snapshot.hasError}');
} else {
return AnimatedList(
key: _globalKey,
initialItemCount: snapshot.data.documents.length,
itemBuilder: (BuildContext context, int index, Animation animation) {
return _buildItem(snapshot.data.documents[index].data['email'], animation);
}
);
}
}
}
)
);
}
Widget _buildItem(String item, Animation animation) {
return SizeTransition(
sizeFactor: animation,
child: Card(
child: ListTile(
title: Text(
item,
style: TextStyle(
fontSize: 20.0,
)
),
),
),
);
}
}
It results into this page.
Emulator Screenshot.
I hope that this solution can help others with similar problems, though I am not sure how costly this could get when it comes to firestore read/writes considering this is just a proof of concept product.
I have a FutureBuilder widget that should wait for data from a firestore collection.
class MyScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: calendarQuery.getCalendarEntry(dateString),
builder: (BuildContext context, AsyncSnapshot snap) {
if (snap.hasData) {
List<Events> recipe = snap.data;
return Scaffold(
appBar: AppBar(
title: Text("Events"),
),
body: Column(
children: <Widget>[
...,
],
),
);
} else {
return LoadingScreen();
}
},
);
}
}
I retrieve a list of events and then for each event I need to fetch some additional details. I tried to do nested Futures and came up with the code below. It generates a Future<Iterable<Future<Detail>>> which ends up as MappedListIterable<DocumentSnapshot,Future<Recipe>> in snap.data and i cannot handle it.
class CalendarQuery<T> {
...
Future<Iterable<Future<Detail>>> getCalendarEntry(String date, String type) async {
return await ref
.where("date", isEqualTo: date)
.getDocuments()
.then((data) {
return data.documents.map((doc) => Document<Details>(path: 'detailCollection/${doc.data["Event"]["SomeId"]}')
.getData());
});
}
}
I think I went wrong here at some points with handling the futures and there is probably a proper way to do this.
Does someone know a way to refactor getCalendarEntry so that it returns a Future<List<T>>? Or maybe there is a better approach to solve this?
You can use Future.wait to create a Future that completes once all Futures from an Iterable have completed.
In your scenario, you would replace return await ref with return Future.wait(ref and a closing bracket where needed, to create a Future that waits for all Details to be retrieved.
I am getting an error that says that the method .length is calling on a null object _genreList.
I am using an async method to get data from a local asset sqlite database to which is a list of genre's. Which then I use ListView.builder in order to display that list on the screen. This is the code to obtain the data...
Future getGenreData() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "asset_sample_sqlite.db");
ByteData data = await rootBundle.load(join("assets", "sample_sqlite.db"));
List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
await new File(path).writeAsBytes(bytes);
Database db = await openDatabase(path);
_genreList = await db.rawQuery('SELECT genre_name[] FROM tbl_genres');
print(_genreList);
await db.close();
}
How do I use this method inside the build Widget method so that I can access the _genreList when I use ListView.builder? like so..
#override
Widget build(BuildContext context) {
return Scaffold(
body: new ListView.builder(
itemCount: _genreList.length, //need to access the genreList here
itemBuilder: (BuildContext context, int index) {
return new Card(
child: new ListTile(
title: new Text("${_genreList[index]}"),
onTap: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => BookPage(id: index),
),
);
}
),
);
}
),
);
}
The end goal here is to display a list of genres (from the tbl_genres in my sqlite database) that will be able to pass through data to the next page to show a list of books (from the tbl_books in my sqlite database) related to that genre.
The whole point of programming asynchronously is that your user interface can stay alive while you are doing time consuming work in the background. So you need (and want) to display something like a CircularProgressIndicator or even a blank page (e.g. a Container), while the application is loading.
There are at least these two ways of doing that:
Make the widget stateful and introduce a state field loading, that you initialize to true and set to false when your data (in another field) is ready. Your code would look like that:
import 'package:flutter/material.dart';
class GenresPage extends StatefulWidget {
#override
_GenresPageState createState() => _GenresPageState();
}
class _GenresPageState extends State<GenresPage> {
bool loading;
List<String> genreNames;
#override
void initState() {
super.initState();
loading = true;
getGenreData();
}
Future getGenreData() async {
final genreData = await actuallyGetThoseNames();
setState(() {
genreNames = genreData;
loading = false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: !loading ? new ListView.builder(
itemCount: genreNames.length,
itemBuilder: (context, index) {
return new Card(
child: new ListTile(
title: new Text("${genreNames[index]}"),
),
);
},
) : CircularProgressIndicator(), // or Container()
);
}
}
Use a FutureBuilder. Therefore you would need to refactor your getGenreData method to return the list as a Future<List<String>>.