how to use Firebase snapshot with rxdart streams - dart

I've a class that has all firebase related functions, and another class to manage state (bloc). How to make them work consistently?.
class UserFirebase {
Stream<List<User>> fetchUsers() {
// I want this function to return a Stream<List<User>> where i can
//listen to in UserBloc
return Firestore.instance.collection('users').snapshots();
}
}
class UserBloc {
UserFirebase _sService;
Observable<List<User>> get users => _sService.fetchUsers();
}
my approach might be not correct but i wanted to describe the problem.

You can watch this tutorial and just replace the source of the data with firebase:
https://www.youtube.com/watch?v=fahC3ky_zW0
It is what I have done an it works for me. The difference with the tutorial is that, I stream a "List" while the stream "UnmodifiableListView". Before you stream, you have to map your data to a list.
You can then use the bloc everywhere you want.

Related

Order of execution on stream listeners?

Is there a way to enforce the order of execution for a broadcast stream with multiple listeners where order of execution matters?
StreamSubscription<T> listen(void onData(T event)?,
{Function? onError, void onDone()?, bool? cancelOnError});
The abstract definition doesn't seem to support it. I was looking for perhaps something like a 'priority' parameter to specify the order of operation.
For example, right now I have a UserController that notify its listeners to do something when the user changes. However, some of the listeners need to be prioritised, but they need to be in their own separate class. Example code:
class UserController{
Stream user;
}
class IndependentControllerA {
//...
userController.user.listen((){
// This needs to be carried out first before everything else
}
//...
}
class IndependentControllerB {
userController.user.listen((){
// This needs to be carried out before A
}
}
What I have thought to overcome this is for UserController to instead register a list of its own Future callbacks that can be awaited in order. See example:
class UserController {
List<Future Function()> callbacks;
void changeUser() async {
callbacks.forEach((callback) => await callback());
}
}
class IndependentControllerA {
//...
userController.callbacks.add(() => print('Do first thing'));
//...
}
class IndependentControllerB {
//...
userController.callbacks.add(() => print('Do second thing'));
//...
}
However, I feel that this is not very elegant, if there is a better innate way to do this already with stream. Is there?
The order that listeners are notified in is definitely not something Dart promises. In practice, it's likely to be ordered in some way depending on the order the listeners were added, but it's not a guarantee, and it might change at any time. (Not really, there's definitely badly written code which depends on the ordering and will break if the ordering changes, but that just means there'll have to be a good reason for the change, not that it can't happen).
I'd write my own "prioritizer" if I had such a specific need. Something like what you have started here. Knowing the specific requirements you have might make it much simpler than making a completely general solution.

How to combine multi data steams in one BLOC?

I have a widget to represent list of stores sorted by nearest to the user current locations also filtering should be applied.
Data in:
Stores data coming from stream of Firestore collection
Current user location from geolacator.
Filtering options from shared preferences
(can be changed any time)
List sorting mode selected by user
Data out: Filtered, sorted, list of stores.
What pattern is best practice in this case?
rxdart : https://pub.dartlang.org/packages/rxdart
if you wanna combine data together you can use
var myObservable = Observable.combineLatest3(
myFirstStream,
mySecondStream,
myThirdStream,
(firstData, secondData, thirdData) => print("$firstData $secondData $thirdData"));
you can combine from ( combineLatest2, combineLatest... combineLatest9 )
or
CombineLatestStream
like this example
CombineLatestStream.list<String>([
Stream.fromIterable(["a"]),
Stream.fromIterable(["b"]),
Stream.fromIterable(["C", "D"])])
.listen(print);
Numbers 2, 3 and 4 are inputs to the bloc that you'd send in through sinks. The bloc listens on those sinks and updates the Firestore query accordingly. This alone might be enough to make Firestore send the appropriate snapshots to the output stream the widget is listening to.
If you can't sort or filter how you want directly with Firestore's APIs, you can use stream.map or apply a StreamTransformer on it. The transformer gives you a lot of flexibility to listen to a stream and change or ignore events on the fly by implementing its bind method.
So you can do something like:
Stream<Store> get stores => _firestoreStream
.transform(filter)
.transform(sort);
Have a look at this page for streams in dart in general, and look into rxdart for more complex stream manipulations.
From personal experience I found having multiple inputs to a block leads to hard to test code. The implicit concurrency concerns inside the block lead to confusing scenarios.
The way I built it out in my Adding testing to a Flutter app post was to create a single input stream, but add markers to the messages notating which data stream the message was a part of. It made testing sane.
In this situation, I think there are multiple asynchronous processing. This implementation can be complicated. And there is a possibility of race condition.
I will implement as follows.
Separate streams of Model from Firestore and user-visible ViewModel in Bloc. Widgets listen to only ViewModel.(eg. with StreamBuilder)
Limit Business logic processing only in Bloc. First, relocate processing with SharedPreferences into Bloc.
Create UserControl class just for user input.
Branch processing depends on user input type of extended UserControl
I hope you this will help you.
For example:
import 'dart:async';
import 'package:rxdart/rxdart.dart';
class ViewModel {}
class DataFromFirestoreModel {}
abstract class UserControl {}
class UserRequest extends UserControl {}
class UserFilter extends UserControl {
final String keyWord;
UserFilter(this.keyWord);
}
enum SortType { ascending, descending }
class UserSort extends UserControl {
final SortType sortType;
UserSort(this.sortType);
}
class Bloc {
final controller = StreamController<UserControl>();
final viewModel = BehaviorSubject<ViewModel>();
final collection = StreamController<DataFromFirestoreModel>();
Bloc() {
controller.stream.listen(_handleControl);
}
_handleControl(UserControl control) {
if (control is UserRequest) {
_handleRequest();
} else if (control is UserFilter) {
handleFilter(control.keyWord);
} else if (control is UserSort) {
handleSort(control.sortType);
}
}
_handleRequest() {
//get location
//get data from sharedPreferences
//get data from firestore
ViewModel modifiedViewModel; // input modifiedViewModel
viewModel.add(modifiedViewModel);
}
handleSort(SortType sortType) {
final oldViewModel = viewModel.value;
//sorting oldViewModel
ViewModel newViewModel; // input sorted oldViewModel
viewModel.add(newViewModel);
}
handleFilter(String keyWord) {
//store data to sharedPreferences
//get data from Firestore
ViewModel modifiedViewModel; // input modifiedViewModel
viewModel.add(modifiedViewModel);
}
}

BreezeJS editing data not working

I was able to follow the instruction on adding data, that part was easy and understandable. But when I tried to follow instructions for editing data, I'm completely lost.
I am following the todo sample, which works quite well, but when I tried to add to my own project using the same principle, nothing works.
in my controller, I have the following:
function listenForPropertyChanged() {
// Listen for property change of ANY entity so we can (optionally) save
var token = dataservice.addPropertyChangeHandler(propertyChanged);
// Arrange to remove the handler when the controller is destroyed
// which won't happen in this app but would in a multi-page app
$scope.$on("$destroy", function () {
dataservice.removePropertyChangeHandler(token);
});
function propertyChanged(changeArgs) {
// propertyChanged triggers save attempt UNLESS the property is the 'Id'
// because THEN the change is actually the post-save Id-fixup
// rather than user data entry so there is actually nothing to save.
if (changeArgs.args.propertyName !== 'Id') { save(); }
}
}
The problem is that any time I change a control on the view, the propertyChanged callback function never gets called.
Here's the code from the service:
function addPropertyChangeHandler(handler) {
// Actually adds any 'entityChanged' event handler
// call handler when an entity property of any entity changes
return manager.entityChanged.subscribe(function (changeArgs) {
var action = changeArgs.entityAction;
if (action === breeze.EntityAction.PropertyChange) {
handler(changeArgs);
}
});
}
If I put a break point on the line:
var action = changeArgs.entityAction;
In my project, it never reaches there; in the todo sample, it does! It completely skips the whole thing and just loads the view afterwards. So none of my callback functions work at all; so really, nothing is subscribed.
Because of this, when I try to save changes, the manager.hasChanges() is always false and nothing happens in the database.
I've been trying for at least 3 days getting this to work, and I'm completely dumbfounded by how complicated this whole issue has been for me.
Note: I'm using JohnPapa's HotTowel template. I tried to follow the Todo editing functionality to a Tee.. and nothing is working the way I'd like it to.
Help would be appreciated.
The whole time I thought the problem was in the javascript client side end of things. Turned out that editing doesn't work when you created projected DTOs.
So in my server side, I created a query:
public IQueryable<PersonDTO> getPerson(){
return (from _person in ContextProvider.Context.Queries
select new PersonDTO
{
Id = _person.Id,
FirstName = _person.FirstName,
LastName = _person.LastName
}).AsQueryable();
}
Which just projected a DTO to send off to the client. This did work with my app in fetching data and populating things. So this is NOT wrong. Using this, I was able to add items and fetch items, but there's no information that allowed the entitymanager to know about the item. When I created an item, the entitymanager has a "createEntity" which allowed me to tell the entitymanager which item to use.. in my case:
manager.createEntity(person, initializeValues);
Maybe if there was a "manager.getEntity" maybe that would help?
Anyways, I changed the above query to get it straight from the source:
public IQueryable<Person> getPeople(){
return ContextProvider.Context.People;
}
Note ContextProvider is:
readonly EFContextProvider<PeopleEntities> ContextProvider =
new EFContextProvider<PeopleEntities>();
So the subscribe method in the javascript checks out the info that's retrieved straight from the contextual object.. interesting. Just wish I didn't spend 4 days on this.

How to share data structures with Polymer on both client and server

Problem:
I have a dart file defining some data structures, which I need to use both for the client and for the server. I'd like to make these data structures observable with Polymer. However, the server cannot include the file because of Polymer, because Polymer includes dart:html.
Context:
I am working on a client/server (REST-full) application, where I want the server to provide the data structures defined available as resources. The client should display these resources, and have the possibility to send the modifications to the server. For that, Polymer is invaluable.
The reason I want to have this library available for the server is that I want the server to be able to validate the resources to be stored.
Possible solutions:
I don't yet know the internals of Polymer enough, but if my data structures could inherit from Map, I could use toObservable in the client side code to make the data structure observable, but instead of accessing by dot notation, I'd have to access members by keys instead, making it rather fragile.
I was wondering if I could use mirrors.dart to add the observable annotation on the client.
Of course, managing duplicate code, is really not a solution.
You can use the observe package.
With ChangeNotifier you initiate the change notification yourself by calling notifyPropertyChange when a value changes. The changes get delivered synchronously.
Observable needs dirtyCheck() to be called to deliver changes.
Polymer calls Observable.dirtyCheck() repeatedly to get the changes automatically.
an example for each
import 'package:observe/observe.dart';
class Notifiable extends Object with ChangeNotifier {
String _input = '';
#reflectable
get input => _input;
#reflectable
set input(val) {
_input = notifyPropertyChange(#input, _input, val + " new");
}
Notifiable() {
this.changes.listen((List<ChangeRecord> record) => record.forEach(print));
}
}
class MyObservable extends Observable {
#observable
String counter = '';
MyObservable() {
this.changes.listen((List<ChangeRecord> record) => record.forEach(print));
}
}
void main() {
var x = new MyObservable();
x.counter = "hallo";
Observable.dirtyCheck();
Notifiable notifiable = new Notifiable();
notifiable.input = 'xxx';
notifiable.input = 'yyy';
}

dart how to create, listen, and emits custom event?

I have class like this :
class BaseModel {
Map objects;
// define constructor here
fetch() {
// fetch json from server and then load it to objects
// emits an event here
}
}
Like backbonejs i want to emits a change event when i call fetch and create a listener for change event on my view.
But from reading the documentation, i don't know where to start since there are so many that points to event, like Event Events EventSource and so on.
Can you guys give me a hint?
I am assuming you want to emit events that do not require the presence of dart:html library.
You can use the Streams API to expose a stream of events for others to listen for and handle. Here is an example:
import 'dart:async';
class BaseModel {
Map objects;
StreamController fetchDoneController = new StreamController.broadcast();
// define constructor here
fetch() {
// fetch json from server and then load it to objects
// emits an event here
fetchDoneController.add("all done"); // send an arbitrary event
}
Stream get fetchDone => fetchDoneController.stream;
}
Then, over in your app:
main() {
var model = new BaseModel();
model.fetchDone.listen((_) => doCoolStuff(model));
}
Using the native Streams API is nice because it means you don't need the browser in order to test your application.
If you are required to emit a custom HTML event, you can see this answer: https://stackoverflow.com/a/13902121/123471
There's a package for it:
https://pub.dev/packages/event
This will be better than using Streams as 'event' is more readable

Resources