d3Roux
d3Roux

Reputation: 316

Angular / Ionic mobile app ios does not fetch from Firebase using angularfire

I am trying to test a little Ionic/Angular sample app on an iOS Emulator.

On the web, all the requests to firestore using angularfire work perfectly fine.

Somehow if I try to execute the same app on the emulator, it keeps loading for the response of the request (if it was a empty response it would say that no results could be retrieved).

Fetching results forever

What is going on? Do i need to set something specifically for the Emulator to work and perform requests to Firestore?

Upvotes: 10

Views: 3355

Answers (4)

Will Madden
Will Madden

Reputation: 6945

[Edit: updated with instructions Firebase JS SDK version 9 (modular)]

This error occurs because Firebase Auth incorrectly detects its environment as a normal browser environment and tries to load remote Google APIs, which results in the error you see in the console:

TypeError: undefined is not an object (evaluating 'gapi.iframes.getContext')

Fortunately, Firebase Auth already has logic to handle running in Cordova/Ionic apps, you just need to tell it which platform it's on.

For Firebase JS SDK version 9 (modular)

Simply import the Cordova Firebase Auth implementation:

import { getAuth } from 'firebase/auth';

For Firebase JS SDK <9 or the compatibility modules (auth/compat)

In capacitor.config set server: { iosScheme: "ionic" }:

// capacitor.config.json
{
  "server": {
    "iosScheme": "ionic"
  }
}

There's a check in the auth/compat library here which, when it sees the URL scheme "ionic://", uses its Ionic/Cordova loading logic, and otherwise falls back to normal browser logic which fails with the error above.

Recent versions of Capacitor changed the URL scheme to "capacitor://" which fails this test but you can override it in your capacitor.config file (see the config option iosScheme).

(See also @alistairheath's comment here).

Upvotes: 5

Jameson Saunders
Jameson Saunders

Reputation: 95

The real problem: firebase-js-sdk on mobile iOS assumes google API (gapi) exists on the window, even when it isn't used.

I found a work around: Mock window.gapi before using firebase auth login:

window['gapi'] = {
  load: (name: string) => Promise.resolve(),
  iframes: {
    getContext: () => {
      return {
        iframe: {
          contentWindow: {
            postMessage: (message: any) => {
              console.log("gapi iframe message:", message);
            }
          }
        }
      }
    }
  }
} as any;

Upvotes: 0

Ejiro Asiuwhu
Ejiro Asiuwhu

Reputation: 200

import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
import { Capacitor } from '@capacitor/core';
import { initializeAuth, indexedDBLocalPersistence } from 'firebase/auth';
import { getAuth } from 'firebase/auth';

const firebaseApp = initializeApp({
 apiKey: process.env.VUE_APP_FIREBASE_API_KEY,
 authDomain: process.env.VUE_APP_FIREBASE_AUTH_DOMAIN,
 databaseURL: process.env.VUE_APP_FIREBASE_DATABASE_URL,
 projectId: process.env.VUE_APP_FIREBASE_PROJECT_ID,
 storageBucket: process.env.VUE_APP_FIREBASE_STORAGE_BUCKET,
 messagingSenderId: 
 process.env.VUE_APP_FIREBASE_MESSAGING_SENDER_ID,
 appId: process.env.VUE_APP_FIREBASE_APP_ID,
});


function whichAuth() {
  let auth
  if (Capacitor.isNativePlatform()) {
    auth = initializeAuth(firebaseApp, {
      persistence: indexedDBLocalPersistence
    })
  } else {
    auth = getAuth()
  }
  return auth
}

export const auth = whichAuth()
const db = getFirestore();

export const auth = whichAuth();
export { firebaseApp, db };

Then in your component, cal your method like this await signInAnonymously(auth);. Don't forget to import the auth we exported at the top.

Upvotes: 9

SeppeDev
SeppeDev

Reputation: 2302

Been struggling a lot with this issue too but I managed to fix it. For those who need help here's my code.

You can delete all Firebase related imports from app.module.ts since this solution only uses Firebase. The packages rxfire and @angular/fire can be removed from your package.json. The only dependency I have is "firebase": "^9.6.1". I used observables for the getObject and list functions since that's what I'm used to and I didn't want to rewrite my original code.

import { Injectable } from '@angular/core';
import { Capacitor } from '@capacitor/core';
import { environment } from '@environment';
import { initializeApp } from 'firebase/app';
import { Auth, getAuth, indexedDBLocalPersistence, initializeAuth, signInWithCustomToken } from 'firebase/auth';
import { Database, getDatabase, onValue, orderByChild, query, ref } from 'firebase/database';
import { Observable, Observer, from } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class FirebaseService {
  private readonly database: Database;
  private readonly auth: Auth;

  constructor() {
    const firebaseApp = initializeApp(environment.firebase);

    if (Capacitor.isNativePlatform()) {
      initializeAuth(firebaseApp, {
        persistence: indexedDBLocalPersistence
      });
    }

    this.database = getDatabase(firebaseApp);
    this.auth = getAuth(firebaseApp);
  }

  connectFirebase(firebaseToken) {
    return from(signInWithCustomToken(this.auth, firebaseToken));
  }

  disconnectFirebase() {
    return from(this.auth.signOut());
  }

  getObject<T>(path: string): Observable<T> {
    return new Observable((observer: Observer<T>) => {
      const dbRef = ref(this.database, path);
      const listener = onValue(dbRef, snapshot => {
        const data = snapshot.val();
        observer.next(data);
      });

      return {
        unsubscribe() {
          listener();
        }
      };
    });
  }

  public list<T>(path: string, orderChildBy?: string): Observable<Array<T>> {
    return new Observable<Array<T>>((observer: Observer<Array<T>>) => {
      const dbRef = ref(this.database, path);
      const dbReference = !orderChildBy ? dbRef : query(dbRef, orderByChild(orderChildBy));

      const listener = onValue(dbReference, snapshot => {
        const data = Object.values<T>(snapshot.val() || {});
        console.log(path, data);
        observer.next(data);
      });

      return {
        unsubscribe() {
          listener();
        }
      };
    });
  }
}

For those who can't see the error message thrown by firebase try the following command in your Safari console to see the error.

window.location.reload()

Upvotes: 1

Related Questions