angularfire
angularfire copied to clipboard
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 ([email protected]) 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
some new observations. i can actually run on ios using
ionic capacitor run ios -l --external
but not directly from xcode. so there is something that xcode is doing while running it
Same here. I also tried with modular. But issue still happens
I got rid of angular/fire. And on vanilla firebase and rxfire works perfectly
"rxfire": "^6.0.3", "firebase": "^9.6.0"
do u have code sample/example on how ur appmodule.ts and a sample call to firebase looks like?
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
but i am not using auth module. does this still apply?
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 firebase/firebase-js-sdk#5552 (comment)
Original Issue https://forum.ionicframework.com/t/firebase-auth-in-sdk-9-does-not-work-on-ios-sim-or-devices/215362/9
This did the trick for me, thanks
as i am initilaizing the firebase in app.module.ts so curious how does your app.module.ts looks like?
@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
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?
@tcns solution is working. You must use AngularFire v7 (not with compatibility mode) to solve this issue.
Yes, but the problem with AngularFire v7 in compatibility mode still exists!
some new observations. i can actually run on ios using
ionic capacitor run ios -l --external
but not directly from xcode. so there is something that xcode is doing while running it
ionic capacitor run ios --external (without livereload) also does not work.
Been struggling a lot with this issue the last days but I managed to fix it. For those who need help here's my code. Thanks to @tcns for the first solution which I added upon.
You can delete all Firebase related imports from app.module.ts
since my 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 use and I didn't want to rewrite my original code.
Hope this can help somebody.
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();
}
};
});
}
}
Came across the same issue and found a temporary solution. I was using "@angular/fire": "^7.2.0"
and was getting the TypeError: undefined is not an object (evaluating 'gapi.iframes.getContext')
when building for IOS using capacitor. By downgrading to "@angular/fire": "^6.1.5"
I can get it working by just removing the /compat
from the imports. In the end my app.module.ts
looks like this:
import { AngularFireModule } from '@angular/fire';
import { AngularFireAuthModule } from '@angular/fire/auth';
import { AngularFireDatabaseModule } from '@angular/fire/database';
import { AngularFirestoreModule } from '@angular/fire/firestore';
@NgModule({
entryComponents: [],
imports: [
BrowserModule,
HammerModule,
IonicModule.forRoot({ mode: "ios" }),
AppRoutingModule,
HttpClientModule,
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
IonicSelectableModule,
AngularFireModule.initializeApp(environment.firebaseConfig),
AngularFireAuthModule,
AngularFireDatabaseModule,
AngularFirestoreModule
],
declarations: [AppComponent],
providers: [InAppBrowser, StatusBar, Broadcaster],
exports: [],
bootstrap: [AppComponent]
})
export class AppModule { }
This required minimal code changes but not ideal I am using an old version of AngularFire.
I'm having the same issue. Any solution instead of using the old version?
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'm experiencing the same problem since I've upgraded to Capacitor 3, Angular 12, Firebase 9, AngularFire 7.2.0 and Ionic 6. It's clear from my logs that it gets stuck while authentifying with Firebase or loading data. I tried to initialize firebase differently as suggested in the previous posts but without success.
However I noticed very different behaviors and it's quite confusing:
-
android: OK on any device
-
ionic capacitor run ios -l --external: OK on any device (iPhone or iPad)
-
emulator via XCode on iPad: OK
-
emulator via XCode iPhone: KO
-
app from the store on iPad: OK
-
app from the store on iPhone: KO
Anyone facing the same issue ?
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 sayingindexedDBLocalPersistence
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!
I'm experiencing the same problem since I've upgraded to Capacitor 3, Angular 12, Firebase 9, AngularFire 7.2.0 and Ionic 6. It's clear from my logs that it gets stuck while authentifying with Firebase or loading data. I tried to initialize firebase differently as suggested in the previous posts but without success.
However I noticed very different behaviors and it's quite confusing:
- android: OK on any device
- ionic capacitor run ios -l --external: OK on any device (iPhone or iPad)
- emulator via XCode on iPad: OK
- emulator via XCode iPhone: KO
- app from the store on iPad: OK
- app from the store on iPhone: KO
Anyone facing the same issue ?
Yes, same issue in my project.
Oh my! I also ran into this problem on iOS for iPhone devices and simulators.
What made it really hard to identify the cause of the problem is that the error seems to get swallowed. 😱
You don't see the message undefined is not an object (evaluating 'gapi.iframes.getContext)
anywhere in the logs.
Instead, you have to use Safari's Web Inspector and enable a breakpoint for uncaught exceptions to reveal it:
I am using Capacitor 3 and the app is using the compatibility mode in @angular/fire@7
and firebase@9
for the AngularFireDatabase
. I observed that the valueChanges()
didn't emit anything, leading me to think that it's a network/configuration-related problem rather than a problem in one of the libraries...
I tested the simple workaround of downgrading to @angular/fire@6
and firebase@8
and can confirm that it resolves the issue.
What makes me a bit sad though is that if I had known that there are such unexpected differences with the compat mode, then I would have migrated to the modular approach right away instead of going the lazy way, which in the end consumed more time for the error identification than if I migrated right away 😅
In any case, a thousand thanks for the hints that the problem originates from the compat mode. 🙌
unfortunatly using @angular/fire@6 is not always a solution because I'm using a library (@robingenz/capacitor-firebase-authentication) that require firebase 9 to work.
Is there a solution to this problem that allows the use of AngularFire and doesn't involve downgrading? I've tried @DeezCashews approach but wasn't successful.
@timstoute I switch to modular way just like @DeezCashews, and it works. Maybe you miss some injection or import.
This issue is causing a bit of headache for my app so I thought I'd do some research and post it here. I still don't have a solution but maybe an underlying cause.
As @Mobiletainment points out, there is an error which shows that "gapi.iframes.getContext" is missing from the window object. Following this research I found a StackOverflow answer suggesting that you mock this object and inject it into the window yourself (1). I could not get this to work, but this perhaps could be a viable solution.
I then questioned why I couldn't see the gapi object on iOS, but I could see it when I ran the same app in the browser or on Android. I believe this could come down to CORS. On Android and the browser, the Ionic app is run from http://localhost but when deployed to iOS it is run from capacitor://localhost (2). I had a look at the domains allowed on my firebase project and sure enough I can enable http:// domain but domains on other protocols. It is possible that the compat mode does not mitigate for this (as previous versions did) and therefore Firebase does not enable the creation of the gapi object.
Just an idea, sources below!
1: https://stackoverflow.com/questions/69778975/angular-ionic-mobile-app-ios-does-not-fetch-from-firebase-using-angularfire 2:https://ionicframework.com/docs/troubleshooting/cors
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.
@alistairheath - amazing, great sleuthing! I confirm this worked in Xcode / Simulator and on local iOS device. Very much appreciated.
Thank you @alistairheath!! That one line solved my days of debugging
@alistairheath this is works but after changing iosScheme, analytics will stop function from what I had found out now.
Hi. Just wanted to know, is there any fix for this instead of workaround?