I'm starting with flutter as I want to port my swift app to Flutter, but I'm getting stuck understanding the pattern Bloc/Repository/Firebase as I'm following the tutorial https://bloclibrary.dev/#/flutterfirestoretodostutorial dough I use the real time database, not Firestore.
My swift app is basically a map where you can add Alerts at your actual coordinates. The Alert get sent to Firebase and the firebase observer on the map updates the map showing the just added alert.
The above tutorial should help me porting my app. I'm just not sure I do understand the logic behind the code.
My concerns are 2:
First. There is an Entity layer between the model object and the firebase object. It is explained that this will facilitate having different Data providers, but I don't really see it facilitating anything. In the Model class there is a toEntity() and a fromEntity() conversion method, and in the Entity class there is a fromSnapshot() and a toDocument() conversion method. I don't see what's the point here. Is it really necessary? What's wrong with doing the conversion directly in the Model class , having different methods for each Data provider?
Second. Inside the TodoBloc I can't follow the logic.
The first event that is sent to the bloc at AppStart is LoadTodos.
BlocProvider<TodosBloc>(
create: (context) {
return TodosBloc(
todosRepository: FirebaseTodosRepository(),
)..add(LoadTodos());
In the mapEventToState() method of TodoBloc that event gets mapped to this Stream:
Stream<TodosState> _mapLoadTodosToState() async* {
_todosSubscription?.cancel();
_todosSubscription = _todosRepository.todos().listen(
(todos) => add(TodosUpdated(todos)),
);
}
So far so good. As I understand this subscribes to the todos() Stream ()
#override
Stream<List<Todo>> todos() {
return todoCollection.snapshots().map((snapshot) {
return snapshot.documents
.map((doc) => Todo.fromEntity(TodoEntity.fromSnapshot(doc)))
.toList();
});
}
and this should be the equivalent of the firebase observer in my swift app. It this part inside the listen closure I'm not sure to understand: (todos) => add(TodosUpdated(todos)) .
This sends to itself (TodoBloc) a TodosUpdated event on which the bloc will map this Stream:
Stream<TodosState> _mapTodosUpdatedToState(TodosUpdated event) async* {
yield TodosLoaded(event.todos);
}
which is this:
class TodosLoaded extends TodosState {
final List<Todo> todos;
const TodosLoaded([this.todos = const []]);
#override
List<Object> get props => [todos];
#override
String toString() => 'TodosLoaded { todos: $todos }';
}
Is this the actual list of Firebase objects? Does the todos() Stream return the entire node every time a new object is added in Firebase?
In my swift app the observer returns only the .childAdded after the first download of the node.
Should I use the firebase_database package that has a FirebaseList class(https://pub.dev/documentation/firebase_database/latest/ui_firebase_list/FirebaseList-class.html) that will just return a List on any change on the node as my observers do in my swift app?
Sorry for this very long and messy question, but I'm quite lost here starting with bloc pattern.
Thank you very much for your time and help.
Ok, I I think I understood the logic behind it, but if you see that I didn't get it right please correct me as at this stage of getting into a new paradigm is very important not to carry any misconceptions.
todos() is the Stream coming from Firebase and returns a List<Todo>.
_mapLoadTodosToState() is the bloc method that attach a bloc listener to todos() and in the .listen(onData) callback, it sends to the bloc an TodosUpdated(todos) event containing the latest list.
TodosUpdated(todos) gets mapped to _mapTodosUpdatedToState, which yields
TodosLoaded(event.todos) , the new state that BlocProvider uses to build the UI.
Thank you and I hope this will help others struggling to master BloC pattern at a more complex level.
Cheers
Related
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);
I ran into issue where Firestore is not reflecting data on client.
Lets say when I create cart manually from Firebase Console it reflects on client side but when I create Cart from client side it does not reflects, although a empty card appears but its null. Assist me on this
Firestore Rules are Public
Data Calling Method
public async Task<ObservableCollection<T>> GetCollection(string collection)
{
var tcs = new TaskCompletionSource<ObservableCollection<T>>();
await DataStore.Collection(collection).Get()
.AddOnCompleteListener(new OnCollectionCompleteListener<T>(tcs));
return await tcs.Task;
}
Thanks
I resolved this issue on my own. While working with Firestore, I understood that if you keep any field null in Firestore, the data inside the document will not be visible. So make sure to not leave any field empty.
I have a flutter app where I use a StreamProvider in main.dart like so:
...
StreamProvider(
catchError: (context, error) {
print(error);
},
initialData: null,
create: (context) => _quizService.getCurrentQuestionStream()),
...
In the app I have a collection called QuizQuestion, each document has a date as it's id, like 2021-12-15, and so every day the app should fetch the QuizQuestion of the day. My stream function looks like this:
Stream<QuizQuestion> getCurrentQuestionStream() {
String currentDate = DateFormat('yyyy-MM-dd').format(DateTime.now());
try {
return _firebaseFirestore
.collection("QuizQuestion")
.doc(currentDate)
.snapshots()
.map((doc) => QuizQuestion.fromJson(doc.data()));
} catch (e) {
return e.message;
}
}
This works to get the current day's QuizQuestion, but if the user has the app open from one day to the next, the stream function is still "subscribed" to fetch the previous date, since it won't define the currentDate variable again. I'm trying to figure out how to solve this, is is possible to some how to listen on a day change in flutter to reinitialize the stream with a new date, or do I need to rethink the backend here?
There is no way to change an existing query. You will have to construct a new query for the new date.
There are two common ways to do this:
Track the current date in your application code, and construct the new query once you detect the new date there.
Write the current date to a fixed location/documentation in the database, e.g. qotd for question/quiz of the day. You could for example do this when you also write the quiz for the next day. Now have your application listen to that document, and when you detect a change in the document, load the quiz for that day.
Both are valid options, and you can embed either option into a stream of your own, which you then feed to the stream builder. I typically prefer the latter as it also gives me a way to control what quiz(es) can be read through security rules.
I have made this stream that fetch 10 user from firebase. and using streamBuilder it shows user in a Listview(Flutter App).
but how to use pagination in such case to get 10 more users after user scroll half of the screen.
this is my stream.which returns a list of UserDetails.
final _refrence = Firestore.instance;
Stream<List<UserDetails>> userStream() {
return _refrence.collection('users').limit(10).snapshots().map((event) =>
event.docs.map((e) => UserDetails.fromJson(e.data())).toList());
}
Use a ScrollController to detect how far you've scrolled. Then when your user have reached your given criteria, increase the limit via for example a parameter passed into the userStream method.
You can check the ScrollController code at this previously asked question.
I am trying to do an app on react-native with a feed. On my main screen, I go fetch the data :
fetchData() {
firebase.database().ref(`/posts/${group}`).on('value', async snapshot => {...}
}
when I want for example to like a comment of the post, I first push the data into firebase with different queries, for example :
export const likeComment = (...) => {
firebase.database().ref(`/posts/${group}/${post}`).update
({
updatedAt: firebase.database.ServerValue.TIMESTAMP
});
firebase.database().ref(`/posts/${group}/${post}/lastComments`).set(...);
But I realized that my first function fetchData was called 3 times.
then I grouped my queries like :
let updates = {}
updates[`/posts/${group}/${post}/lastComments`] = {...};
updates[`/posts/${group}/${post}`] = { ... };
firebase.database().ref().update(updates);
Then, fetchData was called still 2 times.
I was wondering if that was the best way to do it, and why does my function fetchData was still called twice.
Thanks for your help
It's hard to tell from the limited code, but my guess is that fetchData is not actually being called more than once, but instead the snapshot is firing when you make updates to your Firebase database.
The part of your code where you say .on('value', async snapshot => you're setting up a listener to send you updates any time that value changes.
So my guess is that your three invocations are:
The first time you actually call fetchData and set up the
listener
Your .on( snapshot listener firing when you call
update
Your .on( snapshot listener firing again when you
call set
This push-based database workflow is one of the main benefits of Firebase. If you only want to get the data once and not get live updates, you can call .once( instead of .on(
https://firebase.google.com/docs/database/web/read-and-write