I am trying to use StreamProvider and StreamBuilder to pull data from firestore into my app with the code below. I am getting the error "streamusers and "userslist" are not defined as well as "testuser" is not a type. Here is a picture of my firestore databasefirestore setup]1
does anyone know how I can fix this so that it pulls the data from firestore and updates dynamically when new users are added?
Main.dart:
class _MyHomePageState extends State<MyHomePage> {
final auth = FirebaseAuth.instance;
final db = DatabaseService();
#override
Widget build(BuildContext context) {
var user = Provider.of<FirebaseUser>(context);
bool loggedIn = user != null;
final _width = MediaQuery.of(context).size.width;
final _height = MediaQuery.of(context).size.height;
StreamProvider<List<User>>.value(
value: db.streamUsers(user),
child: UsersList(),
),
StreamBuilder<TestUser>(
stream: db.streamTestUser(user.uid),
builder: (context, snapshot) {
var user = snapshot.data;
if (user != null) {
return Stack(...
I also have my db.dart file as so:
class DatabaseService {
final Firestore _db = Firestore.instance;
Future<User> getUser(String id) async {
var snap = await _db.collection('users').document(id).get();
return User.fromMap(snap.data);
}
Stream<User> streamTestUser(String id) {
return _db
.collection('users')
.document(id)
.snapshots()
.map((snap) => User.fromMap(snap.data));
}
}
And finally my user_model.dart file:
class User {
final String name;
final String photourl;
final int totalquestions;
User({this.name, this.photourl, this.totalquestions});
factory User.fromMap(Map data) {
return User(
name: data['name'] ?? '',
photourl: data['photourl'] ?? '',
totalquestions: data['totalquestions'] ?? '',
);
}
}
Try using Builder inside StreamProvider instead of StreamBuilder.
Mine is working using this approach.
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
var user = Provider.of<FirebaseUser>(context);
return StreamProvider<User>.value(
value: db.getUser(user?.uid),
catchError: (_, __) => null,
child: Builder(
builder: (context) {
///Passing UserData Down the Builder
var _userSnapshot = Provider.of<UserData>(context);
///Check UserData Availability
if (_userSnapshot == null) {
return Center(
child: Text('User Empty'),
);
} else {
return Scaffold(
body: Column(
children: <Widget>[
Text(_userSnapshot?.name),
Text(_userSnapshot?.photourl),
Text(_userSnapshot?.totalquestions),
],
),
);
}
},
),
);
}
Related
Im trying to display name of user that is currently logged in app. I have this code right now but its showing me "Document does not exist". I have it from other person that was asking here on stackoverflow but they figured it somehow but didnt post the full correct code so im asking here again. Link Retrieve one field from firebase and display it in a Text Widget
class ProfilePage extends StatefulWidget {
const ProfilePage({Key? key}) : super(key: key);
#override
State<ProfilePage> createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
DocumentReference userName = FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid);
//Variable used to store the name
String name = '';
#override
void initState() {
super.initState();
userName.get().then((DocumentSnapshot ds) {
name = ds['name'];
});
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: <Widget>[
FutureBuilder<DocumentSnapshot>(
future: userName.get(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return const Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data!.exists) {
return const Text("Document does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data = snapshot.data!.data() as Map<String, dynamic>;
return Text("Full Name: ${data['name']}");
}
return const Text("loading");
},
)
],
),
);
}
}
First make sure that "FirebaseAuth.instance.currentUser!.uid" doesn't give null and there is an internet connection.
Then, there are two ways:
Do the following function, and call it in initState method
Future<void> getUserData(String userID) async {
DocumentReference authResult =
FirebaseFirestore.instance.collection('users').doc(userID);
DocumentSnapshot docSnap = await authResult.get();
var data = docSnap.data() as Map<String, dynamic>;
name = data['name'];
}
when you call this function, pass the userId as an argument to the function
OR
Instead of implementing a function and call it in initState you can call didChangeDependencies and make it async
#override
void didChangeDependencies() async {
super.didChangeDependencies();
DocumentReference authResult =
FirebaseFirestore.instance.collection('users').doc(userID);
DocumentSnapshot docSnap = await authResult.get();
var data = docSnap.data() as Map<String, dynamic>;
name = data['name'];
}
Edit
class ProfilePage extends StatefulWidget {
const ProfilePage({Key? key}) : super(key: key);
#override
State<ProfilePage> createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
DocumentReference userName = FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid);
//Variable used to store the name
String name = '';
#override
void didChangeDependencies() async {
super.didChangeDependencies();
DocumentReference authResult =
FirebaseFirestore.instance.collection('users').doc(FirebaseAuth.instance.currentUser!.uid);
DocumentSnapshot docSnap = await authResult.get();
var data = docSnap.data() as Map<String, dynamic>;
name = data['name'];
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: <Widget>[
FutureBuilder<DocumentSnapshot>(
future: userName.get(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return const Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data!.exists) {
return const Text("Document does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data = snapshot.data!.data() as Map<String, dynamic>;
return Text("Full Name: ${data['name']}");
}
return const Text("loading");
},
)
],
),
);
}
}
hello i have json data like this
{
"iduser": 3,
"fname": "joni"
}
I want to display it on the home page
Previously I have created a model class below
usermodel.dart
class UserModel {
int id;
String fname;
UserModel(
this.id,
this.fname,
);
UserModel.fromJson(Map<String, dynamic> response) {
id = response['iduser'];
fname = response['fname'];
}
Map<String, dynamic> toJson() {
return {
'id': id,
'fname': fname,
};
}
}
and I created a service page to interact with api
class AuthService {
String baseUrl = 'https://myurl.com';
Future<UserModel> getUser() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var id = prefs.getInt('id');
var token = prefs.getString('token');
var url = '$baseUrl/users/$id';
var headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token'
};
var response = await http.get(
Uri.parse(url),
headers: headers,
);
print(response.body);
if (response.statusCode == 200) {
var data = jsonDecode(response.body);
UserModel user = UserModel.fromJson(data);
return user;
} else {
print(response.body);
throw Exception('Failed');
}
}
}
home.dart
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Center(
child: Text( ), //get json fname
),
);
}
}
before I run but I get error type
'_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'FutureOr<List<GetUserModel>>'
how to display the fname I get from the service on the home page?
thank you !
Make home.dart a stateful widget and get the data in initstate and store in a variable. Use that variable to display the data here is how
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
void initState() {
super.initState();
getAsync();
}
UserModel user;
getAsync() async {
try {
user = await AuthService().getUser();
} catch (e) {
print(e);
}
if (mounted) setState(() {});
}
#override
Widget build(BuildContext context) {
if (user == null) return Center(child: CircularProgressIndicator());
else
return Container(
color: Colors.white,
child: Center(
child: Text(user.fname), //get json fname
),
);
}
}
You have two options;
Use FutureBuilder
Convert to StatefullWidget
I give you FutureBuilder example;
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder<UserModel>(
future: AuthService().getUser(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting: return Text('Loading....');
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
final data = snapshot.data;
return Container(
color: Colors.white,
child: Center(
child: Text(data.fname), //get json fname
),
);
}
}
},
);
}
}
Also, as far as I can see, there is a problem with the id conversion of the fromJson and toJson methods. Related fields should be 'iduser' according to json data.
class UserModel {
int id;
String fname;
UserModel(
this.id,
this.fname,
);
UserModel.fromJson(Map<String, dynamic> response) {
id = response['iduser'];
fname = response['fname'];
}
Map<String, dynamic> toJson() {
return {
'iduser': id,
'fname': fname,
};
}
}
First, you may want to be consistent in your map key to get the desired result.
You have to replace the key of flutter map version from:
id = response['id']; => id = response['iduser'];
or vice versa.
Now in your homepage, you need to instantiate the AuthService class in order to access the function that will get the specified user.
You need to use FutureBuilder in order to automatically update the Text if the data was fetched.
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
AuthService _authService = AuthService();
return Container(
color: Colors.white,
child: FutureBuilder<User>(
future: _authService.getUser(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.firstName);
}
/// Show some loading artifact while fetching the
/// user data from the server.
else {
return CircularProgressIndicator();
}
},
),
);
}
}
I'm a newbie in Flutter.
I have stored data in firestore in nested collections as you can see hereFirestore1 and hereFirestore2.
I wrote code for that as below:
class Timeline extends StatefulWidget {
#override
_TimelineState createState() => _TimelineState();
}
class _TimelineState extends State<Timeline>
with AutomaticKeepAliveClientMixin<Timeline> {
final eventref = FirebaseFirestore.instance.collection('event');
final slider = SleekCircularSlider(
appearance: CircularSliderAppearance(
spinnerMode: true,
size: 50.0,
));
List<EventCard> event = [];
getEvents() async {
print(user.uid);
QuerySnapshot snapshot =
await eventref.doc(user.uid).collection('post').limit(2).get();
List<EventCard> event = [];
snapshot.docs.forEach((doc) {
print('this is doc ------');
print(doc);
event.add(EventCard.fromMap(doc.data()));
});
return event;
}
#override
bool get wantKeepAlive => true;
#override
Widget build(context) {
return Scaffold(
body: Container(
child: FutureBuilder(
future: getEvents(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return slider;
}
print('reached here:${snapshot.data}');
return ListView(
children: snapshot.data,
);
},
)),
);
}
}
class EventCard extends StatelessWidget {
final String id;
final String event;
final String organiser;
final String description;
final String date;
final String startTime;
EventCard({
this.id,
this.event,
this.organiser,
this.description,
this.date,
this.startTime,
});
factory EventCard.fromMap(Map<String, dynamic> doc) {
return EventCard(
id: doc['id'],
event: doc['Event'],
organiser: doc['Organiser'],
description: doc['Description'],
date: doc['Date'],
startTime: doc['Start Time'],
);
}
#override
Widget build(BuildContext context) {
return Container(
child: Text('Done'),
);
}
}
Sorry for long code, but only response i'm getting with this is here Debug Result
I tried to make 'EventCard' without build method as i saw in a tutorial online, but here it show error 'No concrete implementation of EventCard'
Any help? Thanks in Advance.
I am using Firebase Database to store information regarding my flutter app.
I have manually updated my collections and documents.
But in one instance I want my users to setdata in my documents so it gets reflected in the app for that particular user. But, when the user does setdate it goes and creates new documents which I do not want, I want the user to setdata in the existing document. I did try but no luck.
Here are my codes:
class FirestoreService {
FirestoreService._();
static final instance = FirestoreService._();
Future<void> setData(
{#required String path, Map<String, dynamic> data}) async {
final reference = Firestore.instance.document(path);
await reference.setData(data);
}
abstract class Database {
Future<void> setRackBook(RackBookItems rackBookItems);
}
bool documentCheckBox() => true;
class FirestoreDatabase implements Database {
final String uid;
FirestoreDatabase({#required this.uid}) : assert(uid != null);
final _service = FirestoreService.instance;
#override
Future<void> setRackBook(RackBookItems rackBookItems) async =>
await _service.setData(
path: APIPath.rackBookItems(uid, rackBookItems.id),
data: rackBookItems.toMap());
}
class PageScreen extends StatefulWidget {
final RackBookItems rackBookItems;
final Database database;
const PageScreen(this.rackBookItems, {#required this.database});
static Future<void> show(
BuildContext context, {
Database database,
RackBookItems rackBookItems,
}) async {
final database = Provider.of<Database>(context);
await Navigator.of(context, rootNavigator: true).push(
MaterialPageRoute(
fullscreenDialog: false,
builder: (context) => PageScreen(
rackBookItems,
database: database,
),
),
);
}
#override
_PageScreenState createState() => _PageScreenState();
}
class _PageScreenState extends State<PageScreen> {
final _formKey = GlobalKey<FormState>();
bool _validateAndSaveForm() {
final form = _formKey.currentState;
if (form.validate()) {
form.save();
return true;
}
return false;
}
Future<void> _completed() async {
if (_validateAndSaveForm()) {
try{
final checkBox = widget.rackBookItems?.checkBox ?? documentCheckBox();
final rackBookItems = RackBookItems(checkBox: checkBox);
await widget.database.setRackBook(rackBookItems);
Navigator.of(context).pop();
} on PlatformException catch (e) {
PlatformExceptionAlertDialog(
title: 'Operations failed',
exception: e,
).show(context);
}
}
}
#override
Widget build(BuildContext context) {
final auth = Provider.of<AuthBase>(context, listen: true);
return SafeArea(
child: Scaffold(
body: Column(
children: <Widget>[
StreamBuilder<User>(
stream: auth.onAuthStateChange,
builder: (context, snapshot) {
User user = snapshot.data;
if (snapshot.hasData) {
return Provider<Database>(
create: (_) => FirestoreDatabase(uid: user.uid),
child: Text('Data'),
);[![enter image description here][1]][1]
}
return Center(
child: CircularProgressIndicator(),
);
},
),
Form(
key: _formKey,
child: RaisedButton(
child: Text(
'Done',
style: TextStyle(color: Theme.of(context).accentColor),
),
onPressed: _completed,
),
)
],
),
),
);
}
}
class RackBookItems {
final String id;
final String rackId;
final String title;
final bool checkBox;
const RackBookItems({
this.id,
this.rackId,
this.title,
this.checkBox,
});
Map<String, dynamic> toMap() {
return {
'checkBox': checkBox,
};
}
factory RackBookItems.fromMap(Map<String, dynamic> data, String id) {
if (data == null) {
return null;
}
final String id = data['id'];
final String rackId = data['rackId'];
final String title = data['title'];
final bool checkBox = data['checkBox'];
return RackBookItems(
id: id,
rackId: rackId,
title: title,
checkBox: checkBox,
);
}
}
This is how my firebase looks like.
[1]: https://i.stack.imgur.com/Z07ai.png
Is there any error with the path I have given?
class APIPath {
static String rackBookItems( String uid, String id) =>
'rackBookItems/$id/';
}
You need to use updateData, this method required you to know the document's Document ID
Firestore.instance.collection('rackBookItems').document('book1').updateData({
'newData' : 14
});
If you need to update all of your documents, you can pull all of the documents and use a for loop to update them.
QuerySnapshot qs = await Firestore.instance.collection('rackBookItems').getDocuments();
List<DocumentSnapshot> books = qs.documents;
for (int i = 0; i < books.length; i++){
Firestore.instance.collection('rackBookItems').documents(books[i].documentID).updateData({
'title' : newData
});
}
updateData is good but in case the document does not exist you should use setData and set merge: true
class FirestoreService {
FirestoreService._();
static final instance = FirestoreService._();
Future<void> setData(
{#required String path, Map<String, dynamic> data}) async {
final reference = Firestore.instance.document(path);
await reference.setData(data, merge:true);
}
I'm developing a Flutter mobile application which uses Google APIs. In one of the screens of my application I want to let the user type in a place (city, address, ...) and call the Google Places API to generate a list of suggestions based on user input. Whenever the text input changes a new GET request is issued.
To handle user input I am using a TextEditingController and in order to have a better user experience I want to use FutureBuilder class in order to show a loading spinner when the data is not ready. This is the code:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
class Info extends StatefulWidget {
static const routeName = '/info';
#override
_InfoState createState() => _InfoState();
}
class _InfoState extends State<Info> {
final controller = TextEditingController();
#override
void initState() {
// Start listening to changes.
controller.addListener(buildPredictionList);
super.initState();
}
#override
void dispose() {
// Clean up the controller when the widget is disposed.
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Info'),
),
body: Column(
children: <Widget>[
TextField(
controller: controller,
),
Container(
height: 200,
child: buildPredictionList(),
),
],
),
);
}
Widget buildPredictionList() {
return FutureBuilder(
future: fetchPredictions, // <-- Error! fetchPredictions expects a parameter!
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
Prediction pred = snapshot.data[index];
return Card(
child: ListTile(
leading: Icon(Icons.pin_drop),
title: Text('${pred.description}'),
),
);
},
);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return CircularProgressIndicator();
},
);
}
}
class Prediction {
final String placeId;
final String description;
Prediction({this.placeId, this.description});
factory Prediction.fromJson(Map<String, dynamic> json) {
return Prediction(
placeId: json['place_id'],
description: json['description'],
);
}
}
Future<List<Prediction>> fetchPredictions(String query) async {
const GOOGLE_API_KEY = '...';
final lat = 40.758058;
final lng = -73.985626;
final radius = 2000;
final lang = 'en';
var url =
'https://maps.googleapis.com/maps/api/place/autocomplete/json?input=$query&key=$GOOGLE_API_KEY&location=$lat,$lng&radius=$radius&language=$lang&strictbounds=true';
final response = await http.get(url);
if (response.statusCode == 200) {
var predictionsJson = json.decode(response.body)['predictions'] as List;
List<Prediction> predictions = predictionsJson
.map((predictionJson) => Prediction.fromJson(predictionJson))
.toList();
return predictions;
} else {
throw Exception('Failed to fetch Predictions');
}
}
My async function fetchPredictions expects an argument, which is the query string used for the GET request (so the input address, city, ...). But I cannot wrap this in an anonymous function because the future argument is expecting a return type of Future.
Thanks in advance!