Create user profile page using Bloc, RxDart and Flutter - firebase

My goal is to create an edit profile page in Flutter using the bloc pattern.
I've searched for a better/cleaner way to create a user profile page in Flutter using Bloc, but I can't find anything.
Right now I have to list every field out. Here's an example of 2 fields:
final _firstNameController = BehaviorSubject<String>();
final _lastNameController = BehaviorSubject<String>();
Function(String) get firstNameChanged => _firstNameController.sink.add;
Function(String) get lastNameChanged => _lastNameController.sink.add;
Stream<String> get firstNameStream => _firstNameController.stream;
Stream<String> get lastNameStream => _lastNameController.stream;
String get firstName => _firstNameController.value;
String get lastName => _lastNameController.value;
#override
void dispose() {
_firstNameController?.close();
_lastNameController?.close();
}
There are a lot more fields and I don't want to have all this code if I can avoid it.
I'd prefer to only have 1 user bloc and update the specific field of that user. I've added the following to a user bloc.
final _userFetcher = BehaviorSubject<User>();
Observable<User> get userStream => _userFetcher.stream;
User get user => _userFetcher.value;
Function(User) get changeUser => _userFetcher.sink.add;
#override
void dispose() async {
await _userFetcher.drain();
_userFetcher.close();
}
Here's an example of my user model:
class User {
final Name name;
User.fromJson(Map<String, dynamic> json)
: name = Name.fromJson(json);
Map<String, dynamic> toJson() => {
"first": name.first,
"last": name.last,
};
}
The issue is I can't figure out how to use a textfield to edit the "fist name" and "last name" fields in my "User" model.
Is this possible, am I going about this the wrong way, or should I stick to listing every field out individually?

To individually manage all those streams for each individual fields can be cumbersome. I would recommend you to try out this library flutter bloc . It is a really good one that handles the state management pretty well. You just need to define the states and events and then for each event you can generate a state.
So for example you want to validate password field as the user is typing. You define an Event(eg. PasswordChanged). This will call a method in the bloc. In which you can write your business logic to check the validation.
After your validation logic you can yield a new State(error, succcess). This will cause your UI to be rebuilt and then you can update your UI according to your state.
You should checkout the documentation of this library. It also has some very good examples there.

Thank you everyone for your help!
I ended up being able to edit the nested first and last name fields in my user model I listed above by implementing the following in my textField:
onChanged:(text) {
User thisUser = _bloc.user;
thisUser.name.last = text;
}

Related

Riverpod Flutter: Looking for best way to combine FutureProvider with StateNotifierProvider

I am working on a basic Support Ticket System. I get the Tickets from Firebase (Either as a Stream or Future).
I want to allow some Filtering Options (e.g. sort by Status and Category).
For this, I thought about using A Future Provider to get the List and a StateNotiferProvider to update the List depending on which filter is being used.
This is the code I have so far:
final ticketListStreamProvider =
RP.FutureProvider((_) => FirestoreService.getTicketList());
class TicketListNotifier extends RP.StateNotifier<List<Ticket>> {
TicketListNotifier() : super([]);
void addTicket(Ticket ticket) {
state = List.from(state)..add(ticket);
}
void removeTicket(Ticket ticket) {
state = List.from(state)..remove(ticket);
}
}
final ticketsController =
RP.StateNotifierProvider<TicketListNotifier, List<Ticket>>(
(ref) => TicketListNotifier(),
);
There are multiple issues I have with that. Firstly it doesn't work.
The StateNotifier accepts a List and not a Future<List>. I need to convert it somehow or rewrite the StateNotifier to accept the Future.
I was trying to stay close to one of the official examples.
(https://github.com/rrousselGit/riverpod/tree/master/examples/todos)
Unfortunately, they don't use data from an outside source like firebase to do it.
What's the best approach to get resolve this issue with which combination of providers?
Thanks
You can fetch your ticketlist in your TicketListNotifier and set its state with your ticket list.
class TicketListNotifier extends RP.StateNotifier<List<Ticket>> {
TicketListNotifier() : super([]);
Future<void> fetchTicketList() async {
FirestoreService.getTicketList().when(
//success
// state = fetched_data_from_firestore
// error
// error handle
)
}
}
final ticketsController =
RP.StateNotifierProvider<TicketListNotifier, List<Ticket>>(
(ref) => TicketListNotifier(),
);
Call this method where you want to fetch it /*maybe in your widget's initState method
ref.read(ticketsController.notifier).fetchTicketList();
Now ref.read(ticketsController); will return your ticket list
Since you have the ticket list in your TicketListNotifier's state you can use your add/remove method like this:
ref.read(ticketsController.notifier).addTicket(someTicket);

Getting a single document out of Firestore in Flutter using a StreamProvider

I've followed various guides to successfully get data out of a Firestore collection with a StreamProvider. What I can't quite seem to figure out is how to get a singular document and its associated fields.
For example, let's say I have a collection 'WeatherObs' with a document called '5-13-21' as shown below:
If I wanted to pull in the whole collection with my StreamProvider, I could easily just put it into a list like so:
class FirestoreService {
FirebaseFirestore _db = FirebaseFirestore.instance;
var random = Random();
Stream<List<Weather>> getWeather() {
return _db.collection('TodaysWeather').snapshots().map((snapshot) => snapshot.docs.map((event) => Weather.fromJson(event.data())).toList());
}
I can't for the life of me, however, figure out how if I wanted to just access a single document the same way. I am able to get the document accessible in a key, value pair so I can use fromJson. But when I access the Provider object that calls the method in another class, it always returns null.
Stream<Weather> getWeather() {
return _db.collection('TodaysWeather').doc('5-13-21').snapshots().map((event) => FastFoodHealthEUser.fromJson(event.data()));
}
In the last example, I am not returning a list, but I don't think a list is necessary as I should be able to access the weather object and get access to its attributes like high (type String). Is accessing a single document in the same manner possible?
You can use where for checking on unique field on your documents like this code
Stream<List<VehicleCommentSessionModel>> getSomeoneCommentsList(
{#required String sellerId}) {
return _fbd
.collection('comments')
.where('sellerId', isEqualTo: sellerId)
.snapshots()
.map((qSnap) => qSnap.docs
.map((doc) => VehicleCommentSessionModel.fromJson(doc.data()))
.toList());
}

Flutter/Dart/Firebase - Updated nested map values

I am trying to record which on my users has purchased which ticket in my app. I am using firebase to store my data about my users and giveaways. When a purchase is complete, I am trying to update the relevant giveaway and assign each ticket to a user using their id.
Firstly, I am not sure if my data schema is the most appropriate for what I'm trying to achieve so open to suggestions for editing as I'm still quite new to the flutter world.
Second, here is how my data is currently structured:
Here is how I have structured my code. Here is my SingleBasketItem model:
class SingleBasketItem {
final String id;
final String uid;
final OurGiveAways giveaway;
final int ticketNumber;
SingleBasketItem(this.id, this.uid, this.giveaway, this.ticketNumber);
}
Here is my Shopping Basket model, I have added an Elevated Button to my shopping basket page which, when tapped, will execute the placeOrder() function:
class ShoppingBasket extends ChangeNotifier {
Map<String, SingleBasketItem> _items = {};
Map<String, SingleBasketItem> get items {
return {..._items};
}
void addItem(String id, String uid, OurGiveAways giveaway, int ticketNumber) {
_items.putIfAbsent(
id,
() => SingleBasketItem(id, uid, giveaway, ticketNumber),
);
notifyListeners();
}
void placeOrder(BuildContext context, OurUser user) {
for (var i = 0; i < _items.length; i++) {
FirebaseFirestore.instance
.collection('giveaways')
.doc(_items.values.toList()[i].giveaway.giveawayId)
.update(
{
'individual_ticket_sales'[_items.values.toList()[i].ticketNumber]:
user.uid,
},
);
}
}
}
Below is an image of the results:
By analysing the results it looks like my code is creating a new field with a title of the 1st index character of individual_ticket_sales ("n" because ive bought ticket 1), how can I set the nested "1" (or whatever ticket I choose) to my user id rather than creating a new field? Thanks.
I would recommend to refactor your database structure because the first problem you will hit with that one is that firestore for now does not support updating a specific index for an array field value. You can get more info about that here.
You could get the whole value individual_ticket_sales update it and save it again as whole but it would be just a matter of time when you would hit the problem that multiple users want to update the same value on almost the same time and one of the changes get's lost. Even the usage of transaction would not be 100% safe because of the size of the object and potential multiple changes.
Is it possible for you to store each ticketId as a firestore document in a firestore collection like this:
FirebaseFirestore.instance
.collection('giveaways')
.doc(_items.values.toList()[i].giveaway.giveawayId)
.collection('individual_ticket_sales')
.doc(i)
.update(user.uid);

How can I display on a widget the current user data from Firebase to Flutter

How to display the data submitted by the current user on a widget?
I already tried with streamBuilder and FutureBuilder and nada.
Firebase Users data
Screen where the current user data should be displayed
I could manage to displayed the data (country, username, email, password) from the first Firebase document by using: docs[0]['country'] but that's not right... It needs to be displaying the data from the current user and not always the data from the first document.
I already tried ListView.builder(), but when you scroll down you'll see all data as a list from all users and that's wrong too.
I look forward to seeing your answers.
Thanks in advance for the help guys.
As i understand you want to take data about current or any user. First of all detect userId and you can work this code(I wrote very simple code for my project and you can apply to your code.)
This is Model Class
class MyUser{
String snapshotId;
String userId;
String name;
MyUser({this.userId,this.snapshotId,this.name});
factory MyUser.fromSnaphot(DocumentSnapshot snapshot){
return MyUser(
snapshotId:snapshot.id,
userId:snapshot.data()['userId'],
name:snapshot.data()['name'],
);
}
This is Service Class
final FirebaseFirestore _fBaseFireStore=FirebaseFirestore.instance;
CollectionReference _collectionRef;
UserService(){
_collectionRef=_fBaseFireStore.collection('Users');
}
Stream<MyUser> getUserFromUserId(String userId){
var ref=_collectionRef.where('userId',isEqualTo:userId);
return ref.snapshots().map((event) => MyUser.fromSnaphot(event.docs[0]));
}
Get User Function
final UserService _userService = getIt<UserService>();
return _userService.getUserFromUserId(userId);

Flutter - How to add Firebase-Auth user credentials to new records (FireStore documents)?

I'm trying to create a simple Crud app with Flutter and Firebase which the records (documents created in FireStore) are related to the user who has been Authenticated. Therefore the Crud functions will only be performed by the user who created the record. IE a user will only be able able to edit/update/delete the records they added in the first place.
I have the firebase_auth and crud functions working nicely with firestore. the issues i'm have is with relating the two. I have chosen to use the users email and the unique identifier (i'm not sure if it's better to use the auto generated user id or not). I have created a separate function for simply returning the current user's email as it's being added to the firestore document. The problem is the first time i add a record the user email returns null, If i submit the form again it starts working fine.
String _userEmail;
_getUserAuthEmail() {
FirebaseAuth.instance.currentUser().then((user){
setState((){this._userEmail = user.email;});
});
return this._userEmail;
}
Which is being called from the onPressed event
onPressed: () {
crudObj.addData({
'itemName': this.itemName,
'userEmail': _getUserAuthEmail(),
}).then((result) {
dialogTrigger(context);
}).catchError((e) {
print(e);
});
},
As i'm just starting out please let me know if there is a better approach. Cheers.
You are getting null because you are not waiting for the currentUser method to settle. Change the _getUserEmail method like this:
String _userEmail;
_getUserAuthEmail() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
setState(() {
_userEmail = user.email;
});
return this._userEmail;
}
Also, about this
"I have chosen to use the users email and the unique identifier (i'm not sure if it's better to use the auto generated user id or not)."
I suggest you using the user's uid for saving user related stuff.

Resources