Firestore async load and populate listview flutter - firebase

Hi i am trying to get data from firestore and populate listview, following is my code, but i am getting exception since my async call is not complete how can i wait for that asyn call to get completed and populate my listview
my code is below:
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:test_flutter/pkg/Feed.dart';
class FeedLoader {
final CollectionReference _colReference;
FeedLoader(Firestore _firestore)
: _colReference = _firestore.collection(Feed.getDocumentName()) {}
Future<List<Feed>> load() async {
final List<Feed> feeds = new List<Feed>();
await for (QuerySnapshot qs in _colReference.snapshots) {
for (DocumentSnapshot ds in qs.documents) {
feeds.add(Feed.fromJson(ds.data));
}
return feeds;
}
return feeds;
}
}
This is my Widget
import 'package:flutter/material.dart';
import 'package:test_flutter/pkg/FeedLoader.dart';
import 'package:test_flutter/pkg/Feed.dart';
class FeedWidget extends StatefulWidget {
final FeedLoader feedLoader;
const FeedWidget({Key key, this.feedLoader}) : super(key: key);
createState() => new FeedWidgetState(feedLoader);
}
class FeedWidgetState extends State<FeedWidget> {
final List<Feed> _feeds = new List<Feed>();
final FeedLoader _feedLoader;
final TextStyle fontStyle = const TextStyle(fontSize: 16.0);
FeedWidgetState(this._feedLoader);
#override
Widget build(BuildContext context) {
print(_feedLoader == null);
_feedLoader
.load()
.then((feeds) => () {
print("Got call back now");
_feeds.addAll(feeds);
})
.catchError((e) => handleError(e));
print("Feeds size ${_feeds}");
return _buildListView(context);
}
void handleError(e) {
print("FeedLoaderException ${e}");
}
Widget _buildListView(BuildContext context) {
return new ListView.builder(
padding: const EdgeInsets.all(6.0),
itemBuilder: (context, i) {
if (i.isOdd) return new Divider();
final index = i ~/ 2;
// pagination
// if (index >= contents.length) {
// contents.addAll(generateWordPairs().take(10));
// }
return _buildRowContent(context, _feeds[i]);
},
);
}
Widget _buildRowContent(BuildContext context, Feed content) {
return new ListTile(
title: new Text(
"${content.getTitle()}",
style: fontStyle,
),
);
}
}

You can use StreamBuilder and FutureBuilder to build widgets asynchronously
You could for example do
return new StreamBuilder(
stream: Firestore....snapshot,
builder: (context, snapshot) {
if (snapshot.hasData) {
final feeds = snapshot.data.map(Feed.fromJson);
return new ListView(
....
);
}
},
)

Related

type 'Future<dynamic>' is not a subtype of type 'Widget'. flutter [duplicate]

This question already has answers here:
What is a Future and how do I use it?
(6 answers)
Closed 10 months ago.
The idea
I want to display followers. the page take list of followers user id and then display their username.
Error
when I tried to I get an Error say type 'Future<dynamic>' is not a subtype of type 'Widget'
The issue in this line Text(user["username"]),
Code
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class Following extends StatefulWidget {
final following ;
const Following({Key? key, required this.following}) : super(key: key);
#override
_FollowingState createState() => _FollowingState();
}
class _FollowingState extends State<Following> {
/*attribute*/
var following =[];
bool islouded = false;
var usersData= [];
#override
void initState() {
super.initState();
setState(() {
following = widget.following;
});
getFollowing();
}
void getFollowing() {
for(var user in following){
setState(() {
print(user);
// print(getUser(user));
usersData.add( getUser(user));
});
}
setState(() {
islouded = true;
});
}
getUser(uid)async{
try {
if (uid != null) {
var userSnap = await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.get();
var userData = userSnap.data()!;
// print(userSnap.data()!["username"].toString());
return userData;
}
}catch(e){
showSnackBar(context, e.toString());
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: !islouded?
const Center(
child: CircularProgressIndicator(),
):following.isNotEmpty?
Column(
children: [
for(var user in usersData)
Text(user["username"]),
],
):Text("No following yet!"),
);
}
}
Tried
I tried use FutureBuilder but I did not how to use it right because it return nothing. I believe I'm using it wrong.
the code as follow:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class Following extends StatefulWidget {
final following ;
const Following({Key? key, required this.following}) : super(key: key);
#override
_FollowingState createState() => _FollowingState();
}
class _FollowingState extends State<Following> {
/*attribute*/
var following =[];
bool islouded = false;
var usersData= [];
#override
void initState() {
super.initState();
setState(() {
following = widget.following;
});
getFollowing();
}
void getFollowing() {
for(var user in following){
setState(() {
print(user);
// print(getUser(user));
usersData.add( getUser(user));
});
}
setState(() {
islouded = true;
});
}
getUser(uid) async{
try {
if (uid != null) {
var userSnap = await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.get();
return userSnap;
// print(userSnap.data()!["username"].toString());
// return userData;
}
}catch(e){
print(e.toString());
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: !islouded?
const Center(
child: CircularProgressIndicator(),
):following.isNotEmpty?
Column(
children: [
for(var user in usersData)
FutureBuilder(
future: user,
builder: (context, snapshot){
switch(snapshot.connectionState){
case ConnectionState.none:
return Text("No following yet!");
case ConnectionState.active:
return Text("active");
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
case ConnectionState.done:
print(user);//Instance of 'Future<dynamic>'
print(snapshot);//AsyncSnapshot<Object?>(ConnectionState.done, Instance of '_JsonDocumentSnapshot', null, null)
return Text("username");//i want to display username but getting different error
default:
return Text("No following yet");
}
}
)
// Text(user["username"]),
],
):Text("No following yet!"),
);
}}
Thank you for taking the time reading my question. I hope you have beautiful day like you <3
I feel this may be the culprit:
usersData.add( getUser(user));.
Try this instead: await usersData.add( getUser(user));.
As you call the async method getUser(user) async { ... } it returns a Future, and this Future gets added to the List not the user. This would explain the error complaining about an unexpected Future.

Flutter code loops infinitely even after data is loaded from firebase

I am using the following Flutter and Firebase code to scaffold out a page to a user
import 'package:fgd6ss/models/user.dart';
import 'package:fgd6ss/screens/user/usr_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:convert';
class UsertLanding extends StatefulWidget {
final Map code;
UserLanding({this.code});
User _user = User();
bool dataLoaded = false;
#override
_UserLandingState createState() => _UserLandingState();
}
class _UserLandingState extends State<UserLanding> {
#override
Widget build(BuildContext context) {
bool isValidUser = false;
dynamic userData;
Map codeData = widget.code;
try{
var document = FirebaseFirestore.instance.collection('users').where('id',isEqualTo: codeData['id']);
document.get().then((QuerySnapshot snapshot){
if (snapshot.docs.isNotEmpty) {
if (this.mounted) {
setState(() {
userData = snapshot.docs;
widget._user.name = userData[0]['name'];
widget._user.status = userData[0]['status'];
widget._user.type = userData[0]['type'];
print(widget._user.name);
});
}
}
});
}catch(e) {
print('error firebase data fetch');
}
return Scaffold(
backgroundColor: Color(0xfffefefe),
body: SafeArea(
child: Row(
children: <Widget>[
Container(
padding: EdgeInsets.all(20.0),
child: Text(
widget._user.name,
style: TextStyle(
fontSize: 22.0
),
),
)
]
)
)
);
}
}
As you can see from the above code, I have a print statement inside the query in try. When I run this code, I expect it to run once when the screen loads. But what happens is, the code keeps looping and prints out the users name again and again on the console. Is this expected? If not, what is causing this behaviour? If yes, will it cause increase in the document read quota count on Firebase end.
You have to create a separate method and call this method into your initState(). Build function is run continuously so your print statement is printed in the loop. So try with the below code. initState() method run only once when you reach on to the page
class UsertLanding extends StatefulWidget {
final Map code;
UserLanding({this.code});
User _user = User();
bool dataLoaded = false;
#override
_UserLandingState createState() => _UserLandingState();
}
class _UserLandingState extends State<UserLanding> {
bool isValidUser = false;
dynamic userData;
Map codeData;
#override
initState() {
super.initState();
codeData = widget.code;
getData();
}
getData() {
try {
var document = FirebaseFirestore.instance
.collection('users')
.where('id', isEqualTo: codeData['id']);
document.get().then((QuerySnapshot snapshot) {
if (snapshot.docs.isNotEmpty) {
if (this.mounted) {
setState(() {
userData = snapshot.docs;
widget._user.name = userData[0]['name'];
widget._user.status = userData[0]['status'];
widget._user.type = userData[0]['type'];
print(widget._user.name);
});
}
}
});
} catch (e) {
print('error firebase data fetch');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xfffefefe),
body: SafeArea(
child: Row(children: <Widget>[
Container(
padding: EdgeInsets.all(20.0),
child: Text(
widget._user.name,
style: TextStyle(fontSize: 22.0),
),
)
])));
}
}
You are calling your function in the build method. So whenever the build method will be rebuilt or refreshed, it will call that function again and again. The better approach is to call it in your InitState so it will be called only once. Here is an example that might help you.
#override
initState() {
getData();
super.initState();
}

How can I handle a simple In App Subscription with Flutter?

I'm using the in_app_purchase package and I want to handle a subscription in Flutter.
There is only 1 subscription option and it follows a very common pattern...
I have a section that checks if a Users firebase account 'premium' field is true. If so, it displays the section, if false it displays a 'Subscribe Now' Button.
Currently, on Android, the button doesn't show. On iOS, the button shows but when I click Subscribe - it outputs the PurchaseParams correctly but I'm never prompted for payment. I am testing on a physical device with a sandbox account.
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:wt_flutter/components/trips/blurred_category.dart';
import 'package:wt_flutter/services/globals.dart';
import 'package:wt_flutter/services/models.dart';
import 'package:wt_flutter/shared/loader.dart';
import 'package:wt_flutter/components/trips/initial_airport.dart';
final String proID = 'go_pro_annual';
class TripsScreen extends StatefulWidget {
const TripsScreen({Key key}) : super(key: key);
#override
_TripsScreenState createState() => _TripsScreenState();
}
class _TripsScreenState extends State<TripsScreen> {
// IAP Plugin Interface
InAppPurchaseConnection _iap = InAppPurchaseConnection.instance;
// Is the API available on the device
bool _available = true;
// Subscriptions for sale
List<ProductDetails> _products = [];
// Past purchases
List<PurchaseDetails> _purchases = [];
// Updates to purchases
StreamSubscription _streamSubscription;
#override
void initState() {
_initialize();
super.initState();
}
#override
void dispose() {
_streamSubscription.cancel();
super.dispose();
}
void _initialize() async {
// Check availablility of In App Purchases
_available = await _iap.isAvailable();
if (_available) {
await _getProducts();
await _getPastPurchases();
// List<Future futures = [_getProducts(), _getPastPurchases()];
// await Future.wait(futures);
_verifyPurchase();
// Listen to new purchases
_streamSubscription =
_iap.purchaseUpdatedStream.listen((data) => setState(() {
print('NEW PURCHASE');
_purchases.addAll(data);
_verifyPurchase();
}));
}
}
// Get all products available for sale
Future<void> _getProducts() async {
Set<String> ids = Set.from([proID]);
ProductDetailsResponse response = await _iap.queryProductDetails(ids);
setState(() {
_products = response.productDetails;
});
}
// Gets past purchases
Future<void> _getPastPurchases() async {
QueryPurchaseDetailsResponse response = await _iap.queryPastPurchases();
for (PurchaseDetails purchase in response.pastPurchases) {
if (Platform.isIOS) {
_iap.completePurchase(purchase);
}
}
// Or for consumables
// TODO query the database for state of consumable products
setState(() {
_purchases = response.pastPurchases;
});
}
// Returns purchase of specific product ID
PurchaseDetails _hasPurchased(String productID) {
return _purchases.firstWhere((purchase) => purchase.purchaseID == productID,
orElse: () => null);
}
// Your own business logic to setup a consumable
void _verifyPurchase() {
PurchaseDetails purchase = _hasPurchased(proID);
//TODO serverside verification & record subscription in the database
if (purchase != null && purchase.status == PurchaseStatus.purchased) {
print('Purchase verified');
}
}
/// Purchase a product
void _buyProduct(ProductDetails prod) {
print(prod);
try {
final PurchaseParam purchaseParam = PurchaseParam(productDetails: prod);
// For one time purchase
print(purchaseParam.productDetails.id);
print(purchaseParam.productDetails.price);
print(purchaseParam.productDetails.title);
_iap.buyNonConsumable(purchaseParam: purchaseParam);
print('purchase successful');
} catch (e) {
print(e);
}
}
#override
Widget build(BuildContext context) {
User user = Provider.of<User>(context);
if (user.homeAirport != '') {
return Scaffold(
body: FutureBuilder(
future: Global.tripsRef.getData(),
builder: (BuildContext context, AsyncSnapshot snap) {
if (snap.hasData) {
return Center(
child: Container(
color: Colors.white,
child: ListView(
physics: ClampingScrollPhysics(),
scrollDirection: Axis.vertical,
children: <Widget>[
Text('Wavetrotter Suggestions'),
TripList(),
for (var prod in _products)
_available && !user.premium
? FlatButton(
child: Text('Subscribe'),
onPressed: () => _buyProduct(prod),
color: Colors.green,
)
: SizedBox(height: 0.0),
Text('Trips For You'),
!user.premium ? BlurredCategory() : TripList(),
Text('Biggest Swells'),
!user.premium ? BlurredCategory() : TripList(),
Text('No Wetsuit'),
!user.premium ? BlurredCategory() : TripList(),
],
),
),
);
} else {
return LoadingScreen();
}
},
),
);
} else {
return InitialAirport();
}
}
}

How do I pass data from one page to another?

I want to send the data to another page from come model data. Actually there are data on the getData function so model page, but when I send data to another page, data turn back as null. Can you please help me?
//model.dart
import 'dart:convert';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
class Models {
var data;
Map jsondata = {};
var liste = new List();
Future<void> create(var table , data) async {
Firestore.instance.collection(table).document()
.setData(data).catchError((e){
print(e);
});
}
getData() async{
await Firestore.instance
.collection('musteri')
.document('-LgCmdigOCSqe9j-19lA')
.get()
.then((DocumentSnapshot datas) {
this.jsondata = datas.data;
});
return this.jsondata;
}
}
//chat.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:chat/menu/bottomMenu.dart';
import 'package:chat/models/models.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class Chat extends StatelessWidget {
QuerySnapshot snapshot;
Models model = new Models();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Chat"),
centerTitle: true,
),
body: datas(),
bottomNavigationBar: BottomMenu(),
);
}
datas() {
model.getData();
print(model.jsondata);
}
}
When I use print( this.jsondata ) in the getData ,
result is { 'name':'jhon','surname':'wick' }
but model.jsondata is null in the chat.dart
your ui is built before the model data is populated.So you need to rebuild once you finish pulling in the data.
class Models {
var data;
Map jsondata = {};
var liste = new List();
Future<Map> getData() async{
return Firestore.instance
.collection('musteri')
.document('-LgCmdigOCSqe9j-19lA')
.get()
.then((DocumentSnapshot datas) => datas.data);
}
}
class Chat extends StatefulWidget {
#override
_ChatState createState() => _ChatState();
}
class _ChatState extends State<Chat> {
QuerySnapshot snapshot;
Models _model;
Map _jsondata;
#override
void initState() {
super.initState();
_model = new Models();
_model.getData().then((data) {
setState(() {
_jsondata = data;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Chat"),
centerTitle: true,
),
body: _jsondata == null
? Container()
: Container(
child: Center(
child: Text(
_jsondata["name"],
),
),
),
// bottomNavigationBar: BottomMenu(),
);
}
}
Try some thing like this.

not able to update list after http call ( flutter)

i'm new to flutter
i try to do an http call and then populate a list. The problem is that the http call works fine but the UI doesn't refresh.
i think that the problem is that the framework create ui with the list before the http call is finish. Than when the http call is finished i'm unable to update the ui.
i've search but i didn't find anything.
EDIT--FOUND SOLUTION IN Listview.Builder code here
http call
static Future<Map> getData() async {
try{
http.Response res = await http.get("http://....");
Map data = JSON.decode(res.body);
print(res.body);
return data;
}
catch(exception){
//todo
}
}
main
class _MyHomePageState extends State<MyHomePage> {
List<Widget> _items = new List<GithubCardItem>();
#override
void initState() {
super.initState();
print("start download");
_downloadData();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text(widget.title)),
body: new ListView.builder(
itemBuilder: (BuildContext context, int index) => _items[index],
itemExtent: 128.0,
itemCount: _items.length,
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.add),
onPressed: (){
_downloadData();
},
)
);
}
GithubCard _extractDataFromJson(Map githubCard){
GithubCard card = new GithubCard(githubCard["name"], githubCard["avatar_url"], githubCard["description"], githubCard["stargazers_count"], githubCard["open_issued_count"]);
return card;
}
void _downloadData(){
MyHttpCall.getData().then((jsonData) {
//check if i have card to display
if(jsonData["items"] != null){
for(var githubCard in jsonData["items"]){
setState(() {
GithubCard card = _extractDataFromJson(githubCard);
this._items.add(new GithubCardItem(card));
});
print("adding elements");
}
}
});
}
}
the GithubCardItem is a simple stateless widget that return a text.
There are different ways to use ListView as well as get data from network
here is the sample code for your problem
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List list = new List();
void fetchData() {
getData().then((res) {
setState(() {
list.addAll(res);
});
});
}
#override
void initState() {
super.initState();
fetchData();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new ListView.builder(
itemCount: list.length,
itemBuilder: ((BuildContext _context, int position) {
return new ListTile(
title: new Text( list[position]['login'].toString()),
subtitle: new Text(list[position]['url']),
leading: new Image.network(list[position]['avatar_url']),
);
}),
),
),
floatingActionButton: new FloatingActionButton(
onPressed: fetchData,
tooltip: 'Increment',
child: new Icon(Icons.add),
),
);
}
Future<List> getData() async {
var url = "https://api.github.com/users";
List data = new List();
var httpClient = new HttpClient();
var request = await httpClient.getUrl(Uri.parse(url));
var response = await request.close();
if (response.statusCode == HttpStatus.OK) {
var jsonString = await response.transform(utf8.decoder).join();
data = json.decode(jsonString);
return data;
}else{
return data;
}
}
}
Try this:
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {----------- }
class MyHomePage extends StatefulWidget {-----------}
class _MyHomePageState extends State<MyHomePage> {
List data;
Future<String> getData() async {
var response = await http.get(
Uri.encodeFull("https://api.github.com/users"),
headers: {"Accept": "application/json"});
this.setState(() {
data = json.decode(response.body);
});
return "Success!";
}
#override
void initState() {
this.getData();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Listviews"),
),
body: new ListView.builder(
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, int position) {
return new ListTile(
title: new Text(data[position]['login'].toString()),
subtitle: new Text(data[position]['url']),
leading: new Image.network(data[position]['avatar_url']),
);
},
),
);
}
}
Made bit more simple and it's working. Check the sample here

Resources