Maybe I am over thinking it, but I can't figure out a way to put the results of a Firestore query into a Polymer 3 template. For example:
class MyPage extends PolymerElement {
constructor() {
super();
/* somehow set this.users to the firestore users query results */
}
static get properties() {
return {
users: {
type: String
}
}
}
static get template() {
return html`<div>[[users]]</div>`;
}
}
Using the following code, which does work correctly and print to the console:
var all_users;
const setsRef = firestore.collection("users");
setsRef.get().then(function(querySnapshot) {
var users = [];
querySnapshot.forEach(function(doc) {
users.push(doc.data().verb);
});
all_users = users.join(", ");
console.log("All users: ", all_users);
/* run a function here that sets this.users = all_users */
})
.catch(function(error) {
console.log("Error getting documents: ", error);
});
The problem is, I have to run a function from the Firestore results, while the other is a constructor of an object. I would preferably like to have all of my Firebase queries in an external js file, but this async thought process is confusing me.
Use one of the lifecycle methods to load the users:
https://www.polymer-project.org/3.0/docs/devguide/custom-elements#element-lifecycle
class MyPage extends PolymerElement {
constructor() {
super();
this.users = []
}
static get properties() {
return {
users: {
type: String
}
}
}
static get template() {
return html`<div>[[users]]</div>`;
}
async ready() {
super.ready()
try {
const querySnapshot = await firestore.collection("users").get()
this.users = querySnapshot.map(doc => doc.data().verb).join(", ");
} catch (error) {
console.log(error)
}
}
}
If you don't want to use one of the lifescycle methods, then you can fire your own custom event which your MyPage element can listen for: https://www.polymer-project.org/3.0/docs/devguide/events#custom-events
I'm not familiar with Polymer, so the above is untested and what I figure from reading the docs.
Related
I am working on SwiftUI and using Resolver for Dependency Injection. As a backend I am using Firebase. I've created an AuthSession file that handles all of my user authentication stuff. In the project I also have a number of other repositories that populate data throughout the app. In AuthSession I am creating properties for each repository so that I can start and stop Firestore Listeners on login and logout. In a couple of these repositories I want to access AuthSession through #InjectedObject so that when a user logs in I can be notified and can get updates via Combine. My issues is that when I start the app, it crashes with an odd Firebase error.
AuthSession.swift
class AuthSession: ObservableObject {
let db = Firestore.firestore()
var offerRepository: OfferRepository = Resolver.resolve()
var handle: AuthStateDidChangeListenerHandle?
#Published var currentUser: User?
#Published var loggedIn = false
#Published var currentUserUid = ""
// Combine Cancellable
private var cancellables = Set<AnyCancellable>()
// Intitalizer
init() {
}
func listen() {
print("AuthSession - listen called")
// Monitor Authentication chagnes using Firebase Auth.
handle = Auth.auth().addStateDidChangeListener{ (auth, user) in
// Check to see if a user is returned from a sign in or sign up event.
if let user = user {
// Set loggedIn to true. This will also be set when a new User is created in SignUpView.
print("User Exists.")
self.loggedIn = true
self.currentUserUid = user.uid
self.currentUser = user
} else {
print("Not logged in")
}
}
}
}
Below is OfferRepository. When the line below is added it crashes. If the line is removed it does not crash. I'm not sure why. The Combine code is not included.
Line causing the crash.
#InjectedObject var authSession: AuthSession
OfferRepository.swift
class OfferRepository: ObservableObject {
let db = Firestore.firestore()
private var snapshotListener: ListenerRegistration?
#InjectedObject var authSession: AuthSession
#Published var offers = [Offer]()
private var cancellables = Set<AnyCancellable>()
init() {
startSnapshotListener()
}
func startSnapshotListener() {
if snapshotListener == nil {
self.snapshotListener = db.collection(FirestoreCollection.offers).order(by: "created", descending: true).addSnapshotListener { (querySnapshot, error) in
if let error = error {
print("Error getting documents: \(error)")
} else {
guard let documents = querySnapshot?.documents else {
print("No Offers.")
return
}
self.offers = documents.compactMap { offer in
do {
return try offer.data(as: Offer.self)
} catch {
print(error)
}
return nil
}
}
}
}
}
}
For reference here is my AppDelegate+Registering file.
extension Resolver: ResolverRegistering {
public static func registerAllServices() {
register { AuthSession() }.scope(.application)
register { OfferRepository() as OfferRepository }.scope(.application)
}
}
The app crashed on the line below from the Firestore package.
- (NSString *)keyForDatabase:(NSString *)database {
return [NSString stringWithFormat:#"%#|%#", self.app.name, database];
}
Thread 1: EXC_BAD_ACCESS (code=2, address=0x16d317ff8)
While I can start and stop listeners from login and logout views, I'd prefer to keep this in the AuthSession file. Is there a way around this?
#InjectedObject is intended to be used to inject ObservableObjects into SwiftUI views - see the docs: https://github.com/hmlongco/Resolver#property-wrappers
As you want to reference the AuthenticationService inside your repositories (which are ObservableObjects, you should use #Injected instead.
Here is a snippet from one of my apps:
public class ArtifactRepository: ObservableObject {
// MARK: - Dependencies
#Injected var db: Firestore
#Injected var authenticationService: AuthenticationService
// MARK: - Publishers
#Published public var artifacts = [Artifact]()
// MARK: - Private attributes
private var statusFilter: Status
private var userId: String = "unknown"
private var listenerRegistration: ListenerRegistration?
private var cancellables = Set<AnyCancellable>()
let logger = Logger(subsystem: "dev.peterfriese.App", category: "persistence")
public init(statusFilter: Status = .inbox, liveSync: Bool = true) {
// filtering
self.statusFilter = statusFilter
// observe user ID
authenticationService.$user
.compactMap { user in
user?.uid
}
.assign(to: \.userId, on: self)
.store(in: &cancellables)
// if live sync is on, (re)load data when user changes
if liveSync {
authenticationService.$user
.receive(on: DispatchQueue.main)
.sink { [weak self] user in
if self?.listenerRegistration != nil {
self?.unsubscribe()
self?.subscribe()
}
}
.store(in: &cancellables)
}
}
deinit {
unsubscribe()
}
public func unsubscribe() {
if listenerRegistration != nil {
listenerRegistration?.remove()
listenerRegistration = nil
}
}
public func subscribe() {
if listenerRegistration == nil {
var query = db.collection("artifacts")
.whereField("userId", isEqualTo: self.userId)
if (statusFilter != .all) {
query = query.whereField("status", isEqualTo: statusFilter.rawValue)
}
listenerRegistration = query.order(by: "dateAdded", descending: true)
.addSnapshotListener { [weak self] (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
self?.logger.debug("No documents")
return
}
self?.logger.debug("Mapping \(documents.count) documents")
self?.artifacts = documents.compactMap { queryDocumentSnapshot in
try? queryDocumentSnapshot.data(as: Artifact.self)
}
}
}
}
}
public class AuthenticationService: ObservableObject {
private let logger = Logger(subsystem: "dev.peterfriese.App", category: "authentication")
#Published public var user: User?
private var handle: AuthStateDidChangeListenerHandle?
public init() {
setupKeychainSharing()
registerStateListener()
}
public func signIn() {
if Auth.auth().currentUser == nil {
Auth.auth().signInAnonymously()
}
}
public func signOut() {
do {
try Auth.auth().signOut()
}
catch {
print("error when trying to sign out: \(error.localizedDescription)")
}
}
private let accessGroup = "XXXXXXX.dev.peterfriese.App"
private func setupKeychainSharing() {
do {
let auth = Auth.auth()
auth.shareAuthStateAcrossDevices = true
try auth.useUserAccessGroup(accessGroup)
}
catch let error as NSError {
print("Error changing user access group: %#", error)
}
}
private func registerStateListener() {
if handle == nil {
handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
self.user = user
if let user = user {
if user.isAnonymous {
self.logger.debug("User signed in anonymously with user ID \(user.uid).")
}
else {
self.logger.debug("User signed in with user ID \(user.uid). Email: \(user.email ?? "(empty)"), display name: [\(user.displayName ?? "(empty)")]")
}
}
else {
self.logger.debug("User signed out.")
self.signIn()
}
})
}
}
}
I am struggling with a react native app. I would implement react native firebase dynamic link, but now I am a little lost. I use this method on HomeScreen which working perfectly every times when somebody opens the app.
async componentWillMount() {
try {
let url = await firebase.links().getInitialLink();
if(url) {
let api = "example.com/user/123456";
try {
this.setState({ data: "John Doe" });
this.props.navigation.navigate('Preview', {user: this.state.data })
}
catch {
}
}
}
catch {
}
}
But when the app is already opened this method doesn't work properly. Is there a way where I can trigger a function every time when somebody comes back to the opened app?
Just a tip, you should place your code in componentDidMount so that you do not block the initial (first) render.
You could use AppState to listen out for changes to apps being put in the background/foreground.
componentDidMount() {
this.showPreview();
AppState.addEventListener('change', this.onAppStateChange);
}
componentWillUnmount() {
AppState.removeEventListener('change', this.onAppStateChange);
}
const onAppStateChange = appState => {
// You can check if appState is active/background/foreground
this.showPreview();
}
const showPreview = async (appState) => {
// You can check if appState is active/inactive/background
try {
let url = await firebase.links().getInitialLink();
if(url) {
let api = "example.com/user/123456";
try {
this.setState({ data: "John Doe" });
this.props.navigation.navigate('Preview', {user: this.state.data })
}
catch {
}
}
}
catch(e) {
console.error(e);
}
}
I am integrating Nestjs with firebase admin, the controller is not updating the view. With the service there is not problem, update in real time.
Someone will have some suggestion, what is my wrong in the code?
The Service that inject the controller is:
import { Injectable } from '#nestjs/common';
import * as admin from 'firebase-admin';
export interface Customer {
direction: string,
codLegal: string,
phone: string,
name: string
}
export interface CustomerId extends Customer{
id: string;
}
#Injectable()
export class CustomerService {
constructor() {}
findCustomers(): Promise<any>{
return new Promise((resolve, reject) => {
admin.firestore().collection('/data/LYvBew5FDpjLqcQjA2Ra/info')
.onSnapshot((querySnapshot) => {
const promises: any = [];
querySnapshot.forEach((doc: any) => {
promises.push({
id: doc.id,
data: doc.data() as Customer,
});
});
console.log(promises);
resolve(promises);
})
});
}
}
**The basic controller is: **
import { Controller, Get } from '#nestjs/common';
import { CustomerService } from './services/customer.service';
#Controller('customers') export class CustomerController {
constructor(private readonly customerService: CustomerService) {
}
#Get()
async findAll() {
try {
return await this.customerService.findCustomers();
}catch(err) {
console.log(err);
}
}
}
Talking in terms of HTTP, the controller will not update the view. The view is rendered once you call the findAll route and send to the client.
If you want to show updates to the view in realtime, you should include firebase into your frontend.
I'm trying to apply reflux/ngrx on my current front-end project.
I want to take advantage of this in order to change a slight functionality: Change current user related tasks in order to use a single user state.
Current user related tasks: Currently, I'm using an traditional model in order to achieve user login process... UserService is able to check user credentials. Once it's been checked I store user information on an AppService:
export class LoginComponent implements OnInit {
private fb: FormBuilder;
private form:FormGroup;
private commty: UsersService;
private router: Router;
private appState: AppState;
private alerts: Array<Object>;
constructor()
{
this.alerts = [];
}
ngOnInit():void {
this.form = this.fb.group({
user: ['', Validators.required],
passwd: ['', Validators.minLength(6)]
});
}
public checkPasswd():void {
this.clearAlerts();
this.commty.checkPasswd(this.form.value.mail, this.form.value.passwd)
.subscribe(
(result: any) => {
this.appState.user = result;
this.router.navigate(['/app']);
},
(error: any) => {
this.addAlert(error.message);
}
);
}
private addAlert(message: string): void {
this.alerts.push({type: 'danger', msg: message});
}
public closeAlert(index): void {
this.alerts.splice(index, 1);
};
private clearAlerts(): void {
this.alerts.splice(0, this.alerts.length);
}
}
I'm a bit confused about how to move this code in order to use reflux/ngrx. I'ce read a bit about this topic, nevertheless I'm not quite able to figure out how to move my code. Up to now, I've created an single Store and User interfaces:
store.interface.ts:
export interface IStore {
user: IUser
sources: ISourceRedux;
}
user.interfcae.ts:
export interface IUser {
id: string;
name: string;
username: string;
customer: string;
}
The next step I think I need to do is to create reducers. This step is which I don't quite understand how build this code. Up to now
user.initialstate.ts:
export function initialUserState(): IUser {
return {
id: '',
name: '',
username: '',
customer: '',
sources: []
};
};
user.reducer.ts
export class User {
private static reducerName = 'USER_REDUCER';
public static reducer(user = initialUserState(), {type, payload}: Action) {
if (typeof User.mapActionsToMethod[type] === 'undefined') {
return user;
}
return User.mapActionsToMethod[type](user, type, payload);
}
// ---------------------------------------------------------------
// tslint:disable-next-line:member-ordering
private static mapActionsToMethod = {};
}
Which reducers I should create in order to:
Check credentials.
If credentials are right get this user and update User state store.
If credentials are wrong inform the process has failed.
Perhaps I'm merging concepts... I need some lights...
EDIT
public connect(user: string, currentPasswd: string, extraHttpRequestParams?: any): Observable<UserDTO> {
return this.checkPasswdWithHttpInfo(id, currentPasswd, extraHttpRequestParams)
.map((response: Response) => {
if (response.status === 204) {
return undefined;
} else {
return response.json();
}
}).catch((error: any) => {
if (error.status >= 500) {
return Observable.throw(new Error(error.status));
}
else { //if (error.status >= 400) {
const body = error.json() || '';
const code = body.error || JSON.stringify(body);
const message = body.message || JSON.stringify(body);
return Observable.throw(ApiError.create(code, message));
}
});
}
Ok so this is the next question of your "Integrate ngrx into my code" =).
What you're looking for is : https://github.com/ngrx/effects
The idea behind effects is that an effect let you catch an Action, do side effect (API call or whatever) and you can then dispatch another Action (often success or error).
Flow example to connect a user :
--| [from component] Dispatch action USER_CONNECT
--| [from user.effect.ts]
----| Catch action ofType('USER_CONNECT')
----| Do what you need to do (API call for ex)
----| When the response comes back :
------| If success : Dispatch USER_CONNECT_SUCCESS
------| If error : Dispatch USER_CONNECT_ERROR
Of course when you dispatch either USER_CONNECT_SUCCESS or USER_CONNECT_ERROR you can pass additional data in the payload (for example user information or the error).
Here's a full example :
#Injectable()
export class UsersEffects {
constructor(
private _actions$: Actions,
private _store$: Store<IStore>,
private _userService: UserService,
) { }
#Effect({ dispatch: true }) userConnect$: Observable<Action> = this._actions$
.ofType('USER_CONNECT')
.switchMap((action: Action) =>
this._userService.connect(action.payload.username, action.payload.password)
.map((res: Response) => {
if (!res.ok) {
throw new Error('Error while connecting user !');
}
const rslt = res.json();
return { type: 'USER_CONNECT_SUCCESS', payload: rslt };
})
.catch((err) => {
if (environment.debug) {
console.group();
console.warn('Error catched in users.effects.ts : ofType(USER_CONNECT)');
console.error(err);
console.groupEnd();
}
return Observable.of({
type: 'USER_CONNECT_ERROR',
payload: { error: err }
});
})
);
}
You can take a look into my project Pizza-Sync were I did something similar (except that I don't catch in case of error and do not dispatch if there's an error).
I am trying to list users conversations in an AngularFire2 app, which strucutre is the following:
chats
"chat1": {
title: "First chat",
lastMessage: "Hello world",
members: {
"user1": true,
"user2": true,
}
}
users
"user1": {
name: "Ben",
surname: "Bennsay"
}
"user2": {...}
I am trying to map and list chats in a way that i can easily display the chats participants names bellow the last message.
Question 1: This example differs a little bit from then official recommendation but i feel it would still be valid and scalable. Am i right ?
Question 2: How to actually join members and users to have a users array in my chats list ?
Here is what i have so far.
// retrieve chats "user1" participates in
this.afChatsRef = this.af.database.list(this.datastore(), {
query: {
orderByChild: "/members/user1", // by user id in members
equalTo: true,
}
}).map(chats => {
chats.map(chat => {
// HMMM? WHAT TO DO HERE ?
});
return chats;
});
Thanks, in advance.
UPDATE i have also tried the following, which does not seem quite right (and i cannot access user properties).
this.af.database.list(this.datastore()).map(chats => {
chats.map(chat => {
// chat.users = [];
for (var key in chat.members) {
this.af.database.object("/users/" + key).subscribe(user => {
chat.members[key] = user;
});
}
return chat;
});
console.log(chats);
return chats;
});
You want to return the nested map and fetch the users inside of that. Something like this;
// retrieve chats "user1" participates in
this.afChatsRef = this.af.database.list(...).map(chats => {
// Note the return!
return chats.map(chat => {
// Avoid side effects by storing members separate from the keys
chat.memberData = {};
// Iterate keys and download members
Object.keys(chat.members||{}).forEach(uid => {
// `users` represents a service to cache and load users on demand
chat.memberData[uid] = users.load(uid);
});
});
return chats;
});
Here's a good way to create the users service with caching:
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Rx';
import { AngularFireDatabase } from 'angularfire2/database';
#Injectable()
export class UserProvider {
db: AngularFireDatabase;
users: Map<String, Observable<User>>;
constructor(db: AngularFireDatabase) {
this.db = db;
this.users = new Map();
}
load(userid:string) : Observable<User> {
if( !this.users.has(userid) ) {
this.users.set(userid, this.db.object(`members/${userid}`).valueChanges());
}
return this.users.get(userid);
}
}
export interface User {
name:string;
nick:string;
}
And here is a working example of async joins in AngularFire2.