Firebase: Access to database.....but confused at onDataChange method - database

So i am curious when does onDataChange method occur?
It seems like it is activated when user add new information or change already existed data.
However, what I am trying to do is that, before adding new data, I want to check if the item is existing in database....if there is an identical item, adding new data won't be done, or if there is no such item, then it should be added to database.
so, my actual question is that, this process "Checking all the database items", can it be done without using onDataChange method?

You basically set up a subscription to the "onDataChange" so its actually watching firebase for changes.
But for checking you could literate through the results or do one time query to the exact path your data it held at.
It also may be a better choice to record everything and then remove the data when not needed.
import { AngularFirestore } from 'angularfire2/firestore';
import { AngularFireDatabase, AngularFireList } from 'angularfire2/database';
import { map } from 'rxjs/operators';
import { Observable, Subscription } from 'rxjs';
import firebase as firebase from 'firebase/app';
private mysubscription: Subscription;
public this.items:any = [];
constructor(
public _DB: AngularFireDatabase
) {
try {
//subscription using AngulaFire
this.mysubscription = this._DB.list("myFireBaseDataPath").snapshotChanges().pipe(map(actions => {
return actions.map(action => ({ key: action.key, val: action.payload.val() }));
}))
.subscribe(items => {
this.items = [];
this.items = items.map(item => item);
console.log("db results",this.items);
var icount=0;
for (let i in this.items) {
console.log("key",this.items[i].key);
console.log("val",this.items[i].val);
console.log("----------------------------------);
//checking if something exists
if (this.items[i].key == 'SomeNodePath') {
var log = this.items[i].val;
}
}
} catch (e) {
console.error(e);
}
});
}
ngOnDestroy() {
this.mysubscription.unsubscribe();
}
//or we can do a one time query using just the firebase module
try {
return firebase.database().ref("myFireBaseDataPath").once('value').then(function(snapshot) { return snapshot.val(); })
.then(res => {
for (let myNode in res) {
console.log(res[myNode]);
console.warn(res[myNode].myChildPath);
console.log("----------------------------------);
}
})
.catch(error => console.log(error));
} catch (e) {
console.error(e);
}
//however it may be better practice to log all data and then firebase.database().ref(/logs").remove(); the entire log when not needed
var desc ="abc";
let newPostKey = firebase.database().ref("/logs").push();
newPostKey.set({
'info': desc,
'datetime': new Date().toISOString()
});

When does onDataChange method occur?
The onDataChange method is called for every change in the database reference it is attached to. It is also called for every visit to the database reference it is attached to.
For example,
final FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("some/database/refrence");
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// This method will be fired for any change in the
database.getReference("some/database/refrence") part of the database.
// It will also be fired anytime you request for data in the
database.getReference("some/database/refrence") part of the database
}
#Override
public void onCancelled(DatabaseError databaseError) {
System.out.println("The read failed: " + databaseError.getCode());
// This method will be fired anytime you request for data in the
database.getReference("some/database/refrence") part of the database
and an error occurred
}
});
Before adding new data, I want to check if the item is existing in database....if there is an identical item, adding new data won't be done, or if there is no such item, then it should be added to database.
This can be done by calling the exists() method on the snapshot retrieved from your database query.
Check this stackoverflow question Checking if a particular value exists in the firebase database for an answer to that
So, my actual question is that, this process "Checking all the database items", can it be done without using onDataChange method?
No. The onDataChange method is the callback used to retrieve data from the database. Even if you use the equalTo() method on a query, you'll still have to use the onDataChange method.
I am not a Firebaser Specialist tho. There are folks who work at Firebase on here. They could give you more information
PS: Please make your own research on your questions first before asking. Some questions are already answered in the documentation and on stackoverflow.

Related

Streaming data from local ObjectBox (or Hive) database in a Clean Architecture Style

Maybe someone else has the same issues as I had, so here is how I implemented it, finally. I am showing this for ObjectBox, but some of the methods work for Hive or other databases as well.
My issues was about setting-up the ObjectBox stream and especially about transforming the ObjectBox data model to my domain entities within the stream.
The data streams from the ObjectBox in the data layer, which also contains the repository implementation, through the contracts in the domain layer (I skip use cases here) to the presentation layer, where I am managing the state with flutter-bloc.
The todo domain entity (only showing the id to shorten things):
class Todo extends Equatable{
Todo(this.id);
final String id;
#override
List<Object?> get props => [id,];
}
The data model for ObjectBox including the transformation methods to/from the domain entity
#Entity()
class TodoObjectBox {
#Id()
int id;
final String todoId;
TodoObjectBox ({
this.id = 0,
required this.todoId});
factory TodoObjectBox.toObjectbox(Todo todo, int obId) => TodoObjectBox(
id: obId,
todoId: todo.id,
);
User fromObjectbox() => Todo(
id: todoId,
);
}
The method toObjectBox requires the internal, integer Objectbox id, which I supplement to the method when storing a todo.
Now, in the local datasource implementation, I have the following class to stream the todo's.
#override
Stream<List<Todo>> streamTodos() {
Stream<List<TodoObjectBox>> todoObStream = objectbox.Todo.query()
.watch(triggerImmediately: true).map((query) => query.find());
StreamTransformer<List<TodoObjectBox>, List<Todo>> streamTransformer =
StreamTransformer<List<TodoObjectBox>, List<Todo>>.fromHandlers(
handleData: (List<TodoObjectBox> data, EventSink sink) {
var todos = data!.map((todoOb) {
final Todo todo = todoOb.fromObjectbox();
return todo;
}).toList();
sink.add(todos);
},
handleError: (Object error, StackTrace stacktrace, EventSink sink) {
sink.addError('Something went wrong: $error');
},
handleDone: (EventSink sink) => sink.close(),
);
return streamTransformer.bind(todoObStream);
}
So the stream is generated and then transformed from the data model TodoObjectBox to the domain entity Todo.
In the presentation layer, I instantiate a TodoListBloc with the repository and the event TodoListRequested to start the stream. The bloc then handles this event with the following routine:
Future<void> _onListRequested(
TodoListRequested event,
Emitter<TodoListState> emit,
) async {
emit(state.copyWith(status: () => DataTransStatus.loading));
await emit.forEach<dynamic>(
_todoRepository.streamTodos(),
onData: (todos) {
return state.copyWith(
status: () => DataTransStatus.success,
todosList: () => List.from(
todos.map((t) => TodosListViewmodel.fromDomain(t);
)),
);
},
onError: (Object error, StackTrace stack) {
print('Bloc stream error $error $stack');
return state.copyWith(
status: () => DataTransStatus.failure,
);
},
);
}
I hope this helps someone also trying to stream from Objectbox or another database to the presentation layer using different data models/entities.
Some context to this answer can be found in ResoCoder's TDD tutorial and BlocLibrary's Todo tutorial.

How to push item in a list in Realtime Database with serial indexing like array?

So I am trying to push an item to a list (joinedGrps) every time a user creates a group, but when I try to push any item to the list, the value set to an automatically generated id
But I want it to store like an array with indexing starts with 0 like the below example
CODE
const RDBuserRef = realDB.ref(`users/${userInfo.userId}/joinedGrps`);
const newRDBref = RDBuserRef.push(grpName).catch((err) => {
console.log(err);
});
So is it possible to do?
Alternatives methods are welcomed but it should be in Realtime Database
You need to use the set() method instead of the push() one, which "generates a unique key every time a new child is added to the specified Firebase reference".
In order to know which key to use, you need to first read the Array, add a new element and then, write it back.
The following will do the trick:
const groupRef = realDB.ref(`users/${userInfo.userId}/joinedGrps`);
groupRef
.get()
.then(snapshot => {
if (snapshot.exists()) {
const grpArray = snapshot.val();
grpArray.push('...');
groupRef.set(grpArray);
} else {
groupRef.set({ 0: 'Elem#1' });
}
})
.catch(function (error) {
console.error(error);
});
HOWEVER, if several users are able to update the database node you need to use a transaction. Since you need to read the array before writing it back, using a transaction is the only way to ensure that there are no conflicts with other users writing to the same location at the same time.
groupRef.transaction(currentArray => {
if (currentArray === null) {
return { 0: 'Elem#1' };
} else {
currentArray.push('....');
return currentArray;
}
}

NoSQL creating and accessing different store references

I am trying to create a NoSQL database for a Flutter app that has different stores for different activities done throughout the day (sleep, exercise, eating, etc.). I don't want to hard code the stores and want to be able to add and delete stores as needed.
The issue I'm encountering is the intMapStoreFactory.store() requires a static input but the inputs for SembastActivityRepository cannot be static. Is there a way to create and access custom store names from outside this WembastActivityRepository class?
Thank you!
import 'package:get_it/get_it.dart';
import 'package:sembast/sembast.dart';
import './activity.dart';
import './activity_repository.dart';
class SembastActivityRepository extends ActivityRepository {
final activityName;
SembastActivityRepository({this.activityName});
final Database _database = GetIt.I.get();
final StoreRef _store = intMapStoreFactory.store(activityName);
#override
Future<int> insertActivity(Activity activity) async {
return await _store.add(_database, activity.toMap());
}
#override
Future updateActivity(Activity activity) async {
await _store.record(activity.id).update(_database, activity.toMap());
}
#override
Future deleteActivity(int activityId) async {
await _store.record(activityId).delete(_database);
}
#override
Future<List<Activity>> getAllActivities() async {
final snapshots = await _store.find(_database);
return snapshots
.map((snapshot) => Activity.fromMap(snapshot.key, snapshot.value))
.toList(growable: false);
}
}
I'm not sure what you mean by static but anyway your code could not compile and I strongly suggest using Strong mode to get compile time recommendation. A StoreRef is just a declaration of a store and its key and value type, you can create/and re-created a StoreRef at any time, the store itself is only created when you add records to it.
If it is just to to get your code to compile, initialize _store in the constructor:
class SembastActivityRepository {
final String activityName;
final StoreRef<int, Map<String, dynamic>> _store;
SembastActivityRepository({this.activityName})
: _store = intMapStoreFactory.store(activityName);
// ...
}

Exporting an array within an ".then" doesnt work

I'm new to NodeJS and are only familiar with Java. I'm trying to create a file that creates objects based on a database and adds them to an array. This array I want to be able to export so that I can use it throughout the whole program, but when I try to export the array it doesn't work. I've tried googling and understanding but haven't come across anything that was helpful unfortunately.
I hope that someone can help me understand
I've tried calling module.exports after the ".then" call, but it just returns an empty array because its async.
I've also tried calling module.exports = teams inside the .then call but it didn't work neither.
var teams = [];
function assignTeamsToClasses() {
return new Promise((resolve, reject) => {
getAllTeamsInDb((teamList) => {
teamList.forEach((aTeam) => {
let newTeam = new Team(aTeam['teamid'], aTeam['teamname'], aTeam['teamrank']);
teams.push(newTeam);
});
resolve();
});
})
}
assignTeamsToClasses().then(() => {
module.exports = teams;
});
main.js
var teams = require('./initialize.js');
console.log(teams);
I expect it to return all teams that are in the database. I know that array is not empty when called within the ".then" call, but the export part does not.
Simple
the sequence require() + console.log() is synchronous
assignTeamsToClasses() is asynchronous, i.e. it updates teams at some unknown later point in time.
You'll have to design your module API to be asynchronous, e.g. by providing event listener interface or Promise interface that clients can subscribe to, to receive the "database update complete" event.
A proposal:
module.exports = {
completed: new Promise(resolve =>
getAllTeamsInDb(teams => {
const result = [];
teams.each(aTeam =>
result.append(new Team(aTeam.teamid,
aTeam.teamname,
aTeam.teamrank)
)
);
resolve(result);
})
),
};
How to use it:
const dbAPI = require('./initialize.js');
dbAPI
.completed
.then(teams => console.log(teams))
.catch(error => /* handle DB error here? */);
Every caller who uses this API will
either be blocked until the database access has been completed, or
receive result from the already resolved promise and proceed with its then() callback.

Calling arrays in Firebase using Angular 2

I would like to know how to call a Firebase array using Angular 2. In my example here, I have an array addnote in my Firebase DB. There are two separate iterations of Do the dishes, and I would like to print them out to my HTML's unordered list.
The [] in my private addsnotes throws errors, and I didn't really expect otherwise. In the absence of understanding how to output the array, I am using it to illustrate what I am trying to achieve. I have also marked the relevant area where the call is being made.
My rainbow.component.html
<div><ul>
<li *ngFor="let addsnote of addsnotes">{{addsnote}}</li>
</ul></div>
My firebase schema:
My rainbow.component.ts
export class Rainbow implements OnInit{
private addsnotes: [];
private username: string;
ngOnInit(){
var self = this;
var user = firebase.auth().currentUser;
var getUserInfo = firebase.database().ref('users/' + user.uid);
setTimeout(acquisition, 1000);
function acquisition(){
if (user){
getUserInfo.once('value', function(snapshot){
self.username = snapshot.val().username;
self.addsnotes = snapshot.val().addnote; //incorrect
});
}
}
}
}
If you want the AngularFire2 makes is easy to tune into the power of Observables so you can detect changes on the firebase end and auto-update your user notes. With AngularFire2, your solution would look more like this...
rainbow.component.html
<div>
<ul>
<li *ngFor="let addsnote of addsnotes$ | async">{{ addsnote.$value }}</li>
</ul>
</div>
rainbow.component.ts
import { Injectable } from '#angular/core';
import { AngularFire } from 'angularfire2';
import { Observable } from 'rxjs/Observable';
export class RainbowComponent implements OnInit {
private addsnotes$: Observable<string[]>;
private username$: Observable<string>;
constructor (
private af: AngularFire
) {}
ngOnInit () {
let user = firebase.auth().currentUser,
userNamePath = `users/${user.uid}/username`,
notesPath = `users/${user.uid}/addnote`;
this.username$ = this.af.database.object(userNamePath);
this.addsnotes$ = this.af.database.list(notesPath);
}
}
You will need the async pipe when using Observables in your template HTML. It will auto subscribe to extract the data. The big difference with this and the previous code is that anytime your addsnotes data changes, it will automatically show the changes on the HTML view. If you want to keep it like the previous code where you are limiting it to one call using once('value'), you can add a .take(1) to the end of this.af.database.list(notesPath) to just take the list values one time.
In addition, I would recommend adding a sub field to your notes such as order so that you can sort your list in an order that you want. You can find info on how to sort with AngularFire2 here.
Hope this helps.
If you wanted to stick with Web Firebase API (no angularfire2), to get the addsnotes to work, it might look something like this.
ngOnInit () {
let user = firebase.auth().currentUser;
// Used ES6 arrow function instead, which is built into Typescript
setTimeout(() => {
// Make sure user and user.uid are defined
if (user && user.uid) {
let userRef = firebase.database().ref(`users/${user.uid}`);
userRef.once('value', (snapshot) => {
let userInfo = snapshot.val() || {};
this.username = userInfo['username'];
this.addsnotes = [];
// Traverse each child for 'addnote'
snapshot.child('addnote').forEach((childSnapshot) => {
let addnoteKey = childSnapshot.key,
addnote = childSnapshot.val();
addnote['id'] = addnoteKey; // Saving the reference key if you want reference it later
self.addsnotes.push(addnote);
});
}
}
}, 1000);
}

Resources