angularfire: AngularFire does not work on ios with ionic capacitor 3

Version info

Angular:@angular/common”: “~12.1.1”, “@angular/core”: “~12.1.1”,

"@angular/forms": "~12.1.1",
"@angular/platform-browser": "~12.1.1",
"@angular/platform-browser-dynamic": "~12.1.1",
"@angular/router": "~12.1.1",
"@capacitor/android": "^3.3.2",
"@capacitor/app": "^1.0.6",
"@capacitor/core": "3.3.2",
"@capacitor/ios": "^3.3.2",
"@ionic/angular": "^5.5.2",

"rxjs": "~6.6.0",
"tslib": "^2.2.0",
"xcode": "^3.0.1",
"xml-js": "^1.6.11",
"zone.js": "~0.11.4"

Firebase: “firebase”: “^9.5.0”, AngularFire:@angular/fire”: “^7.2.0”,

Other (e.g. Ionic/Cordova, Node, browser, operating system): ionic info

Ionic:

Ionic CLI : 6.18.1 (/usr/local/lib/node_modules/@ionic/cli) Ionic Framework : @ionic/angular 5.9.1 @angular-devkit/build-angular : 12.1.4 @angular-devkit/schematics : 12.1.4 @angular/cli : 12.1.4 @ionic/angular-toolkit : 4.0.0

Capacitor:

Capacitor CLI : 3.3.2 @capacitor/android : 3.3.2 @capacitor/core : 3.3.2 @capacitor/ios : 3.3.2

Cordova:

Cordova CLI : 10.0.0 (cordova-lib@10.1.0) Cordova Platforms : android broken, ios 5.1.1 Cordova Plugins : no whitelisted plugins (0 plugins total)

Utility:

cordova-res (update available: 0.15.4) : 0.15.3 native-run : 1.5.0

System:

ios-deploy : 1.9.4 ios-sim : ios-sim/9.0.0 darwin-x64 node-v14.17.0 NodeJS : v14.17.0 (/usr/local/bin/node) npm : 7.24.0 OS : macOS Monterey Xcode : Xcode 13.1 Build version 13A1030d

How to reproduce these conditions

Failing test unit, Stackblitz demonstrating the problem

Steps to set up and reproduce ionic build ionic cap copy ionic cap sync open xcode and either run on device or simulator

Sample data and security rules

Debug output

2021-12-08 13:25:36.266937-0600 App[2469:682932] KeyboardPlugin: resize mode - native 2021-12-08 13:25:36.280309-0600 App[2469:682932] InAppPurchase[objc] Initialized. ⚡️ Loading app at capacitor://localhost… ⚡️ [log] - onscript loading complete ⚡️ To Native -> Device getId 109505367 ⚡️ [log] - Ionic Native: deviceready event fired after 48 ms ⚡️ TO JS {“uuid”:“14A77728-365E-4EDC-8F78-7CD1F6CC8CA0”} To Native Cordova -> AppVersion getVersionNumber AppVersion678940142 [“options”: []] ⚡️ WebView loaded SplashScreen.hideSplash: SplashScreen was automatically hidden after default timeout. You should call SplashScreen.hide() as soon as your web app is loaded (or increase the timeout). Read more at https://capacitorjs.com/docs/apis/splash-screen#hiding-the-splash-screen ⚡️ To Native -> App addListener 109505368

** Errors in the JavaScript console ** above pasted nothing else

** Output from firebase.database().enableLogging(true); **

** Screenshots **

Expected behavior

should run fine as it runs on android

Actual behavior

does not run on ios but run on android

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 7
  • Comments: 49

Commits related to this issue

Most upvoted comments

I was also having this issue when building for an iOS emulator and actual iOS device, but adding this to the app.component.ts worked for me.

import { Component } from '@angular/core';
import { Capacitor } from '@capacitor/core';
import { initializeApp } from 'firebase/app';
import { indexedDBLocalPersistence, initializeAuth } from 'firebase/auth';
import { environment } from 'src/environments/environment';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss'],
})
export class AppComponent {
  constructor() {
    const app = initializeApp(environment.firebase);
    if (Capacitor.isNativePlatform) {
      initializeAuth(app, {
        persistence: indexedDBLocalPersistence
      });
    }
  }
}

This is where I found the fix and a sample code https://github.com/firebase/firebase-js-sdk/issues/5552#issuecomment-929580662

Original Issue https://forum.ionicframework.com/t/firebase-auth-in-sdk-9-does-not-work-on-ios-sim-or-devices/215362/9

Based on my comment above, I believe I have found a workaround. If calls from capacitor://localhost are being rejected by firebase which then prevents the creation of the “gapi” object on the browser window, what if we could use a different protocol instead of “capacitor://”.

Fortunately capacitor.config.ts has a feature do exactly this, just set server .iosScheme to “ionic” like so: const config: CapacitorConfig = { ... server: { iosScheme: 'ionic' } };

I tried this and compat worked immediately on iOS! I’m still nervous to push this into production as I unsure what unintended consequences may arise from using the “ionic” protocol, which was used with Cordova, instead of the “capacitor” protocol.

Ok, so I got this working finally with the following dependencies:

    "@angular/fire": "^7.2.0",
    "firebase": "^9.6.4",
    "rxfire": "^6.0.3"

After reading this ticket and many others I decided to remove all references to compat but no luck, it still wouldn’t connect on iOS. I was going to try vanilla like others described and create a service but didn’t like that I’d lose DI.

Instead I did this:

import { provideFirebaseApp, getApp, initializeApp, FirebaseApp } from '@angular/fire/app';
import { provideAuth, initializeAuth } from '@angular/fire/auth';
import { provideFirestore, getFirestore } from '@angular/fire/firestore';

import { indexedDBLocalPersistence } from 'firebase/auth';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    provideFirebaseApp(() => initializeApp(environment.firebase)),
    provideFirestore(() => getFirestore()),
    provideAuth(() => initializeAuth(getApp(), { persistence: indexedDBLocalPersistence })),
    BrowserModule,
    IonicModule.forRoot(),
    AppRoutingModule
  ],
  providers: [
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
    AuthService
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

Notice I imported indexedDBLocalPersistence from 'firebase/auth' instead of @angular/fire/auth. I was getting compile time errors saying indexedDBLocalPersistence wasn’t available. Something for the AngularFire team to look at.

Now I can use like so:

import { Component } from '@angular/core';
import { Auth, signInWithEmailAndPassword, UserCredential, User } from '@angular/fire/auth';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss'],
})
export class AppComponent {
  constructor(private afAuth: Auth) {
    signInWithEmailAndPassword(this.afAuth, email, password);
  }
}

Hope this saves someone the hours I wasted.

I have the same error. Launching angular fire app on real iOS device I get this error:

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

is it possible to solve this and continue using angular fire?

Ok, so I got this working finally with the following dependencies:

    "@angular/fire": "^7.2.0",
    "firebase": "^9.6.4",
    "rxfire": "^6.0.3"

After reading this ticket and many others I decided to remove all references to compat but no luck, it still wouldn’t connect on iOS. I was going to try vanilla like others described and create a service but didn’t like that I’d lose DI.

Instead I did this:

import { provideFirebaseApp, getApp, initializeApp, FirebaseApp } from '@angular/fire/app';
import { provideAuth, initializeAuth } from '@angular/fire/auth';
import { provideFirestore, getFirestore } from '@angular/fire/firestore';

import { indexedDBLocalPersistence } from 'firebase/auth';

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    provideFirebaseApp(() => initializeApp(environment.firebase)),
    provideFirestore(() => getFirestore()),
    provideAuth(() => initializeAuth(getApp(), { persistence: indexedDBLocalPersistence })),
    BrowserModule,
    IonicModule.forRoot(),
    AppRoutingModule
  ],
  providers: [
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
    AuthService
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

Notice I imported indexedDBLocalPersistence from 'firebase/auth' instead of @angular/fire/auth. I was getting compile time errors saying indexedDBLocalPersistence wasn’t available. Something for the AngularFire team to look at.

Now I can use like so:

import { Component } from '@angular/core';
import { Auth, signInWithEmailAndPassword, UserCredential, User } from '@angular/fire/auth';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss'],
})
export class AppComponent {
  constructor(private afAuth: Auth) {
    signInWithEmailAndPassword(this.afAuth, email, password);
  }
}

Hope this saves someone the hours I wasted.

Work like a charm 👍 Thank you!

is there a solution for this?

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

Error Screenshot

@alistairheath - amazing, great sleuthing! I confirm this worked in Xcode / Simulator and on local iOS device. Very much appreciated.

@moblizeit Sorry for late answer. Here app.module.ts. I just deleted all firebase initializers and providers.

@NgModule({
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    FormsModule,
    SwiperModule,
    IonicModule.forRoot(),
    IonicStorageModule.forRoot(),
    ServiceWorkerModule.register('ngsw-worker.js', {
      enabled: environment.production
    })
  ],
  declarations: [AppComponent],
  providers: [InAppBrowser, StatusBar, Broadcaster],
  exports: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

Instead of this I created small service

import {Injectable, OnInit} from '@angular/core';
import {environment} from '../../environments/environment';
import 'firebase/firestore';
import 'firebase/database';
import { initializeApp } from 'firebase/app';
import { initializeAuth, indexedDBLocalPersistence, Auth, browserLocalPersistence } from 'firebase/auth';
import { getDatabase, Database } from '@firebase/database';
import { FirebaseApp } from '@firebase/app';

@Injectable({
  providedIn: 'root'
})
export class FirebasecustomService implements OnInit {
  app: FirebaseApp;
  database: Database;
  auth: Auth;
  constructor() {
    this.app = initializeApp(environment.firebaseConfig);
    this.auth = initializeAuth(this.app, {
      persistence: indexedDBLocalPersistence
    });
    this.database = getDatabase(this.app);
  }

  ngOnInit(): void {

  }
}

And just use vanilla rxfire. Like this.

import {BehaviorSubject, forkJoin, from, Observable, of, pipe, Subject} from 'rxjs';
import {catchError, first, map, mergeMap, switchMap, tap} from 'rxjs/operators';
import {User} from '@firebase/auth';
import {Database, ref, set, remove} from 'firebase/database';
import {objectVal} from 'rxfire/database';

...

  constructor(
    public storage: Storage,
    public firebaseCustom: FirebasecustomService
  ) {
    this.db = firebaseCustom.database;
  }

...

  loadStories(): Observable<Story[]> {
    return objectVal<Story[]>(ref(this.db, '/stories'))
        .pipe(
            mergeMap(val => of(Object.values(val)))
        );
  }

I dont know if its normal code. Because I’m not js dev. Just my pet project. But its working both on sim and real device