Flutter firebase, use a reference in a document to call another document - flutter

I am using MultiProvider Stream with a database service with the firebase calls and a data model and using to and fromJson.
I am calling the Firestore User in my main file, and have a wrapper to call the user's document which holds the reference to the company they belong to, once the user data is retrieved we then call the company document in a userType screen, I then use the reference and pass it to the getter of the company document, but the document is being called before the document ref is passed.
I have tried to change the Stream to a Future but then I get an error on Provider.
Database Service
Stream<Companies> streamCompanies() {
_userRef.get().then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
print('HERE IS THE SNAPSHOT');
print(documentSnapshot.get('companyID'));
thisUserCompany = documentSnapshot.get('companyID');
}
});
debugPrint('Database Service => GETTING COMPANY STREAM');
debugPrint(thisUserCompany);
return _database
.collection('companies')
.doc(thisUserCompany)
.snapshots()
.map(
(snapshot) =>
Companies.fromJson(snapshot.data() as Map<String, dynamic>),
);
}
Comapnies Class
class Companies {
final String uid;
final String companyName;
final String logoForPDF;
Companies ({required this.uid, required this.companyName,
required this.logoForPDF});
Companies.fromJson(Map<String, dynamic> json)
: uid = json['uid'],
companyName = json['companyName'],
logoForPDF = json['logoForPDF'];
Map<String, dynamic> toJson() => {
'uid': uid,
'companyName': companyName,
'logoForPDF': logoForPDF,
};
factory Companies.initialData() {
return Companies(
uid: 'Loading',
companyName: 'Loading',
logoForPDF: 'Loading',
);
}
}
MultiProvider
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
#override
Widget build(BuildContext context) {
final user = Provider.of<UserData?>(context);
return user != null
? MultiProvider(providers: [
StreamProvider<Companies?>.value(
initialData: Companies.initialData(),
value: user != null
? DatabaseService(uid: user.uid, companyID: user.companyID)
.streamCompanies()
: null),
], child: const ProfileSelector())
: const CircularProgressIndicator();
}
}

Ok so a little more perseverance I now have this working but I do not know if this is the correct way of doing this.
In my database file, I have changed the code to the following
Stream<Companies> streamCompanies() async* {
await _userRef.get().then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
thisUserCompany = documentSnapshot.get('companyID');
}
});
yield* _database
.collection('companies')
.doc(thisUserCompany)
.snapshots()
.map((snapshot) =>
Companies.fromJson(snapshot.data() as Map<String, dynamic>),
);
}

Related

Flutter - user model is NULL even after binding it using bindStream

I am trying to add a shopping cart functionality to my app wherein the UserModel contains fields name,email,uid and cart. I have created an AuthController extends GetxController where I have created userModel isntance and then in setInitialScreen function I am binding the userModel to a function "listenToUser()" which is a Stream and recieves snapshot from firestore and maps it to the userModel. But on printing the userModel I see null being printed in the console meaning the data is not getting binded which is causing problems as i can't access the cart stored in the userModel.
Edit: I saw that I need to attach obs to userModel like: Rx<model.User>? userModel = model.User().obs; but there's a problem that all the fields in this model are required and how can i pass these field values when they have not yet been intialized.
Console output:
AuthController code
class AuthController extends GetxController {
static AuthController instance = Get.find();
late Rx<User?> _user;
Rx<model.User>? userModel;
#override
void onReady() {
super.onReady();
_user = Rx<User?>(firebaseAuth.currentUser);
_user.bindStream(firebaseAuth.authStateChanges());
ever(_user, _setInitialScreen);
}
_setInitialScreen(User? user) {
if (user == null) {
Get.offAll(() => LoginScreen());
} else {
userModel?.bindStream(listenToUser());
Get.offAll(() => const HomeScreen());
print(userModel); // PRINTING USER MODEL TO SEE IF ITS NULL
// userModel?.bindStream(listenToUser());
}
}
// registering the user
void registerUser(String username, String email, String password) async {
try {
if (username.isNotEmpty && email.isNotEmpty && password.isNotEmpty) {
// save our user to our auth and firebase firestore
UserCredential cred = await firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
model.User user = model.User(
name: username, email: email, uid: cred.user!.uid, cart: []);
await firestore
.collection('users')
.doc(cred.user!.uid)
.set(user.toJson());
} else {
Get.snackbar(
'Error Creating Account',
'Please enter all the fields',
);
}
} catch (e) {
Get.snackbar(
'Error Creating Account',
e.toString(),
);
}
}
void loginUser(String email, String password) async {
try {
if (email.isNotEmpty && password.isNotEmpty) {
await firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
print('log success');
} else {
Get.snackbar(
'Error Logging in',
'Please enter all the fields',
);
}
} catch (e) {
Get.snackbar(
'Error Logging in',
e.toString(),
);
}
}
updateUserData(Map<String, dynamic> data) {
print("UPDATED");
firestore.collection('users').doc(_user.value?.uid).update(data);
}
Stream<model.User> listenToUser() => firestore
.collection('users')
.doc(_user.value?.uid)
.snapshots()
.map((snapshot) => model.User.fromSnap(snapshot));
}
User Model code:
class User {
// static const UID = "uid";
// static const NAME = "name";
// static const EMAIL = "email";
String uid;
String name;
String email;
List<CartItemModel> cart;
User(
{required this.name,
required this.email,
required this.uid,
required this.cart});
Map<String, dynamic> toJson() =>
{"name": name, "email": email, "uid": uid, "cart": cart};
// static User fromSnap(DocumentSnapshot snap) {
static User fromSnap(DocumentSnapshot snap) {
var snapshot = snap.data() as Map<String, dynamic>;
return User(
name: snapshot['name'],
email: snapshot['email'],
uid: snapshot['uid'],
cart: _convertCartItems(snapshot['cart'] ?? []));
}
// List<CartItemModel> _convertCartItems(List cartFromDb) {
static List<CartItemModel> _convertCartItems(List cartFromDb) {
List<CartItemModel> _result = [];
// logger.i(cartFromDb.lengt);
print(cartFromDb.length);
cartFromDb.forEach((element) {
_result.add(CartItemModel.fromMap(element));
});
return _result;
}
}
Also I referred this github for shopping cart functionality but I have made some changes to make it null safe: cart functionality github
Use Rxn<T>() for nullable rx:
final _user = Rxn<User>();
Then on onInit():
_user.bindStream(firebaseAuth.authStateChanges());

Getting Null check operator used on a null value flutter when I set user details for the logged in user

Once I click on login button, I redirect the user to AuthScreen.dart, where it serves the loginScreen if the user is not logged in but if the user is logged in then it serves the appropriate home screen according to the user type and sets user details as provider values.
So, if the user is logged in then it goes to the snapshot.hasData().
The code for AuthScreen.dart is as follows:
class AuthScreen extends StatefulWidget {
const AuthScreen({ Key? key }) : super(key: key);
#override
State<AuthScreen> createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> {
#override
Widget build(BuildContext context){
return Scaffold(
body: StreamBuilder<dynamic>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if(snapshot.hasData){
return FutureBuilder(
future: AuthViewModel.getUserDetails(context),
builder: (context, snapshot) {
if(snapshot.data == 'doctor'){
return DoctorHomeScreen();
}
else{
return StudentHomeScreen();
}
},
);
}
else{
return LoginScreen();
}
},
),
);
}
}
AuthViewModel.getUserDetails asks from another function for user details, which is returned from firebase and then sets those values for provider values, which is as follows:
static Future<String> getUserDetails(BuildContext context) async {
Map<String, dynamic> map = await MyUser.getUserDetails();
if (map['type'] != 'not found') {
context.read<CurrentUser>().name = map['name'];
context.read<CurrentUser>().email = map['email'];
}
return map['type'];
}
The function called from the second code block calls for a function which returns user details from firebase.
MyUser.getUserDetails():
static Future<Map<String, dynamic>> getUserDetails() async {
final user = await FirebaseAuth.instance.currentUser;
if(user != null){
final users = await FirebaseFirestore.instance.collection('users').where('email', isEqualTo: user.email).get();
for(var user in users.docs){
final authUser = user.data();
return {'type': authUser['userType'], 'name': authUser['name'], 'email': authUser['email']};
}
}
return {'type': 'not found'};
}
This is the model class, CurrentUser, which stores the user details for the logged in user:
class CurrentUser with ChangeNotifier{
String _name = '';
String _email = '';
String get name => _name;
String get email => _email;
set name(String name){
_name = name;
notifyListeners();
}
set email(String email){
_email = email;
notifyListeners();
}
}
However, when I call context.read<CurrentUser>(), I get Null check operator used on a null value. Please help out.

The method '[]' was called on null: firebase flutter

I need to retrieve User Data from Firebase and use a builder to pass on the data to UI. When I run the apps, I method is called in on Null.
I tried many ways to call firebase data but I keep receive error message on provider or on calling the data NULL.
The error is most likely coming from the method _getProfileData() below.
_getProfileData(AuthNotifier authNotifier) async {
final uid = await Provider.of(context, listen: false).authNotifier.getCurrentUID();
await Provider.of(context, listen: false)
.collection('Users')
.document(uid)
.get().then((result) {
user.isAdmin = result.data['isAdmin'];
});
}
When I made the changes below by using Provider, another error appears with Provider not working.
final uid = await Provider.of<authNotifier>(context, listen: false).getCurrentUID();
I placed the getter in the API.
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
// GET UID
Future<String> getCurrentUID(User user, AuthNotifier authNotifier) async {
return (await _firebaseAuth.currentUser()).uid;
}
// GET CURRENT USER
Future getCurrentUser(User user, AuthNotifier authNotifier) async {
return await _firebaseAuth.currentUser();
}
Stream<String> get onAuthStateChanged => auth.onAuthStateChanged.map(
(FirebaseUser user) => user?.uid,
);
I structured User Data as below.
class User {
List favorites = [];
String documentID;
String displayName;
String email;
String password;
bool isAdmin;
User({
this.favorites,
this.documentID,
this.displayName,
this.email,
this.password,
this.isAdmin,
});
factory User.fromFirestore(DocumentSnapshot document) {
Map data = document.data;
return User(
favorites: data['favorite'] ?? [],
documentID: document.documentID,
displayName: data['displayName'] ?? '',
email: data['email'] ?? '',
isAdmin: data['isAdmin'] ?? false,
);
}
// get admin => null;
Map<String, dynamic> toMap() {
return {
'displayName': displayName,
'email': email,
'isAdmin': isAdmin,
};
}
}

Flutter: Parsing JSON data and showing in App

I am very new to Flutter and Dart.
I have a signup page and I would like to show error in the App. My backend page is returning the errors and status in JSON format. Like below.
{"errors":{"Password1":"Password could not be empty",
"Email1":"Invalid Email Format",
"Name":"Your name must be between 3 to 30 characters!"},
"success":false}
I created a file for JSON parsing like below.
import 'dart:convert';
Signup signupFromJson(String str) => Signup.fromJson(json.decode(str));
String signupToJson(Signup data) => json.encode(data.toJson());
class Signup {
Errors errors;
bool success;
Signup({
this.errors,
this.success,
});
factory Signup.fromJson(Map<String, dynamic> json) => Signup(
errors: Errors.fromJson(json["errors"]),
success: json["success"],
);
Map<String, dynamic> toJson() => {
"errors": errors.toJson(),
"success": success,
};
}
class Errors {
String password1;
String email1;
String name;
Errors({
this.password1,
this.email1,
this.name,
});
factory Errors.fromJson(Map<String, dynamic> json) => Errors(
password1: json["Password1"],
email1: json["Email1"],
name: json["Name"],
);
Map<String, dynamic> toJson() => {
"Password1": password1,
"Email1": email1,
"Name": name,
};
}
Now I need to show this data to App after Async call.
Future userRegistration() async{
try{
// Showing CircularProgressIndicator.
setState(() {
visible = true ;
});
// Getting value from Controller
String name = nameController.text;
String email = emailController.text;
String password = passwordController.text;
// SERVER API URL
var url = 'http://192.168.100.10:8080/app/registerexec.php';
// Store all data with Param Name.
var data = {'name': name, 'email': email, 'password' : password};
// Starting Web API Call.
var response = await http.post(url, body: json.encode(data));
// Getting Server response into a variable.
final message = signupFromJson(response.body);
if(response.statusCode == 200){
setState(() {
visible = false;
});
}
// Showing Alert with Response JSON Message.
}catch(e){
return userRegistration();
}
}
How can I show the JSON data to SnackBar?
Edit
I managed to get the data in Print after manually defining it. Like below. But I want to automate it. So, if there are any errors it can show and if its successful then a different message.
print(message.errors.email1);
print(message.errors.name);
print(message.errors.password1);
print(message.success);
you could use FutureBuilder at your snackBar. I've edited from the code available here:
class SnackBarPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: userRegistration,
initialData: '',
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
// snapshot.data = yourData from your userRegistration
// print(snapshot.data) to show your data here
return snackBar = SnackBar(
content: Text('Yay! A SnackBar!'),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
},
)
};
)
},
),
}
}

How to use json and serialization with firebase and bloc? Error: Converting object to an encodable object failed

this is my cloud firestore looks like:
Error Message: Unhandled Exception: Converting object to an encodable
object failed: Photography
used jsonSerialization for my database
import 'package:json_annotation/json_annotation.dart';
part 'Model.g.dart';
#JsonSerializable()
class Photography{
String couplePhoto;
String female;
String image_url;
String info;
String male;
AllImages all_images;
Photography();
factory Photography.fromJson(Map<String, dynamic> json) => _$PhotographyFromJson(json);
Map<String,dynamic> toJson() => _$PhotographyToJson(this);
}
#JsonSerializable()
class AllImages {
List<String> imageUrl = List<String>();
AllImages();
factory AllImages.fromJson(Map<String, dynamic> json) => _$AllImagesFromJson(json);
Map<String,dynamic> toJson() => _$AllImagesToJson(this);
}
By running flutter pub run build_runner build in the project root, I generated JSON serialization code for my Photography and AllImages whenever they are needed.
Model.g.dart
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'Model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Photography _$PhotographyFromJson(Map<String, dynamic> json) {
return Photography()
..couplePhoto = json['couplePhoto'] as String
..female = json['female'] as String
..image_url = json['image_url'] as String
..info = json['info'] as String
..male = json['male'] as String
..all_images = json['all_images'] == null
? null
: AllImages.fromJson(json['all_images'] as Map<String, dynamic>);
}
Map<String, dynamic> _$PhotographyToJson(Photography instance) =>
<String, dynamic>{
'couplePhoto': instance.couplePhoto,
'female': instance.female,
'image_url': instance.image_url,
'info': instance.info,
'male': instance.male,
'all_images': instance.all_images
};
AllImages _$AllImagesFromJson(Map<String, dynamic> json) {
return AllImages()
..imageUrl = (json['imageUrl'] as List)?.map((e) => e as String)?.toList();
}
Map<String, dynamic> _$AllImagesToJson(AllImages instance) =>
<String, dynamic>{'imageUrl': instance.imageUrl};
After that, I created the DB class,
How to use the model class?
class DB {
final db = Firestore.instance;
// Stream<QuerySnapshot> initStream() {
// return db.collection('photography').snapshots();
// }
getPhotography() async {
return db.collection('photography')
.document("0yUc5QBGHNNq6WK9CyyF")
.setData(jsonDecode(jsonEncode(Photography)));
}
}
DB db = DB();
my photography_bloc class
class PhotographyBloc extends BlocBase{
//PhotographyBloc(){
// db.initStream().listen((data) => inFirestore.add(data));
//}
PhotographyBloc(){
init();
}
Photography photography;
//final _firestoreController = StreamController<Photography>();
//Stream<Photography> get outFirestore => _firestoreController.stream;
//Sink<Photography> get inFirestore => _firestoreController.sink;
final _firestoreController = StreamController<Photography>();
Stream<Photography> get outFirestore => _firestoreController.stream;
Sink<Photography> get inFirestore => _firestoreController.sink;
void init() async{
photography = db.getPhotography();
inFirestore.add(photography);
}
#override
void dispose() {
_firestoreController.close();
}
}
my StreamBuilder Widget
How to get data using JSON serialization
child: StreamBuilder<Photography>(
stream: bloc.outFirestore,
initialData: null,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
children: buildItem(snapshot.data, bloc));
// children: snapshot.data.documents
// .map<Widget>((doc) => buildItem(doc, bloc))
// .toList());
} else {
return SizedBox();
}
}),
builderItem() method,
buildItem(Photography doc, PhotographyBloc bloc) {
...
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: FadeInImage.assetNetwork(
placeholder: "assets/images/photography.jpg",
image: doc.couplePhoto,
// image: doc.data['couplePhoto'],
fit: BoxFit.fill,
),
),
According to the package source :
/// Writes to the document referred to by this [DocumentReference].
///
/// If the document does not yet exist, it will be created.
///
/// If [merge] is true, the provided data will be merged into an
/// existing document instead of overwriting.
Future<void> setData(Map<String, dynamic> data, {bool merge = false}) {
return Firestore.channel.invokeMethod<void>(
'DocumentReference#setData',
<String, dynamic>{
'app': firestore.app.name,
'path': path,
'data': data,
'options': <String, bool>{'merge': merge},
},
);
}
You must give a <String, dynamic> Map to setData(x) method.
So in your case you should maybe do it like this :
getPhotography() async {
return db.collection('photography')
.document("0yUc5QBGHNNq6WK9CyyF")
.setData(photography.toJson());
}