Setting up Firebase with SwiftUI some questions - firebase

So I'm watching some videos about how to setup Firebase with SwiftUI but none of them really seem to be the most recent?
The latest video I found just does two steps, importing Firebase, then making an init in my app struct that calls FirebaseApp.configure() like this:
import SwiftUI
import Firebase
#main
struct FirestoreDemoApp: App {
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
But Firebase has a bunch of code and tells me to set it up like this:
import SwiftUI
import FirebaseCore
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
#main
struct YourApp: App {
// register app delegate for Firebase setup
#UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
NavigationView {
ContentView()
}
}
}
}
But both of those still seem to work? Could someone maybe explain what exactly the second one is with all this AppDelegate and what's the point of it? Why is the first option which is much shorter also working just fine?

Firebaser here - actually, I am the person who made the change to our onboarding flow that recommends using the #UIAppDelegateAdaptor approach.
TL;DR: Both approaches work fine, but the #UIAppDelegateAdaptor approach covers more use cases.
Initialising Firebase in your app's initialiser works for most of Firebase's APIs, such as Firestore, RTDB, (most of) Authentication, etc. However, some APIs, such as FCM or Phone Number Authentication need an App Delegate.
Since we didn't want to make the onboarding flow more complicated than necessary (e.g. by asking people if they are planning to use FCM or Phone Auth, or making them read a lengthy blog post), we decided to take the safe route and recommend the slightly more complicated-looking approach that covers all use cases.
For an even more detailed explanation, check out my blog post, Firebase and the new SwiftUI 2 Application Life Cycle.

Related

SwiftUI ask Push Notifications Permissions again

So I have push notifications implemented in my App and when the app first starts up, its asks users if they would like to allow push notifications (this implementation works fine as expected).
If this user disallows the push notifications, is it possible to have a button in the app which allows the user to click on and it would ask to allow permissions again?
This is what im trying to achieve:
SettingsView
//IF PUSH NOTIFICATIONS NOT ENABLED, SHOW THIS SECTION
Section (header: Text("Push Notifications")) {
HStack {
Image(systemName: "folder")
.resizable()
.frame(width: 20, height: 20)
VStack(alignment: .leading) {
Text("Enable Push Notifications").font(.callout).fontWeight(.medium)
}
Spacer()
Button(action: {
checkPushNotifications()
}) {
Text("View").font(.system(size:12))
}
}
}
In my Push Notification Function:
class PushNotificationService: NSObject, MessagingDelegate {
static let shared = PushNotificationService()
private let SERVER_KEY = "myserverkey"
private let NOTIFICATION_URL = URL(string: "https://fcm.googleapis.com/fcm/send")!
private let PROJECT_ID = "my project name"
private override init() {
super.init()
Messaging.messaging().delegate = self
}
func askForPermission() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (granted: Bool, error: Error?) in
if granted {
self.refreshFCMToken()
} else {
// Maybe tell the user to go to settings later and re-enable push notifications
}
}
}
func refreshFCMToken() {
InstanceID.instanceID().instanceID { (result, error) in
if let error = error {
print("Error fetching remote instance ID: \(error)")
} else if let result = result {
print("Remote instance ID token: \(result.token)")
self.updateFCMToken(result.token)
}
}
}
func updateFCMToken(_ token: String) {
guard let currentUser = Auth.auth().currentUser else { return }
let firestoreUserDocumentReference = Firestore.firestore().collection("users").document(currentUser.uid)
firestoreUserDocumentReference.updateData([
"fcmToken" : token
])
}
}
What im trying to achieve is if the user HAS NOT enabled notification only then ask them the option to reenable in SettingsView.
No you cannot. However, a good UI/UX design will be careful before burning the one-time chance of asking for permissions. Instead, use a user friendly UI to explain why you need certain permissions. For example, I often found it frustrating to implement a permission display view, and handle various async permission requests in a seperate view model. So I recently made a SwiftUI package:
PermissionsSwiftUI
                  
PermissionSwiftUI is a package to beautifully display and handle permissions.
EmptyView()
.JMPermissions(showModal: $showModal, for: [.locationAlways, .photo, .microphone])
For a SINGLE line of code, you get a beautiful UI and the permission dialogs.
It already supports 7 OUT OF 12 iOS system permissions. More features coming 🙌
Full example
struct ContentView: View {
#State var showModal = false
var body: some View {
Button(action: {showModal=true},
label: {Text("Ask user for permissions")})
.JMPermissions(showModal: $showModal, for: [.locationAlways, .photo, .microphone])
}
}
To use PermissionsSwiftUI, simply add the JMPermission modifier to any view.
Pass in a Binding to show the modal view, and add whatever permissions you want to show.
The short answer is no, you can't ask the user again if he once disabled the push-notifications for your app.
What you can do, is navigating the user to the settings in their phone to allow push-notifications again.
The code snippet in SwiftUI for the button would be:
Button(action: {
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}, label: {
Text("Allow Push")
})
I would also refer to this question: How to ask notifications permissions if denied?

Firebase FCM onLaunch code not running functions when clicking on notification

I am using FCM to deliver notifications that once clicked take the user to a specific page on the app. Currently, the onMessage and onResume functions work perfectly fine but on launch does not.
I include the
<key>FirebaseAppDelegateProxyEnabled</key>
<false/>
and
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}
because I am also using flutter local notifications.
I have already tried removing these lines but they do not make a difference.
Ok so the problem I had was that when the on launch code fires my app was loading other data and functions e.t.c when on the loading splash page. What I had to do was when on launch fires I saved the notification in a variable. Then I executed the code I wanted after the splash init was finished
onLaunch: (Map<String, dynamic> notification) async {
print("onLaunch: $notification");
///Saving the notification to use once the rest of the initialising and
///Loading is done
launchNotification = notification;
},
Then once the loading and the initialising of other processes had finished I ran the ran this function
onLaunchFunction() async {
await Future.delayed(Duration(milliseconds: 100));
Map tempNotification = launchNotification;
if (launchNotification != null) {
launchNotification = null;
///The rest of the function
}
}
I added an a future delayed just to make sure that my code would run whilst the initialising. This may not be needed

How do I access Firebase Firestore from Dart VM / Dart integration tests?

I am writing a Flutter app that has fairly complex logic over the Firebase Firestore documents.
I am trying to write unit tests using flutter_test that actually execute this logic against the database (I know this is technically an integration test). This is because this logic has a lot of edge-cases I can only be sure are working if tested against the real database.
This seems to be an impossible task.
The cloud_firestore package can only run inside the phone because of how the authentication was implemented.
The firebase package has 2 "implementations". One can only work on the browser, and the other one, the Dart VM one, which is a low level wrapper around the REST API, is almost completely undocumented.
The 2. firebase package for Dart VM has this example:
import 'package:firebase/firebase_io.dart';
void main() {
var credential = ... // Retrieve auth credential
var fbClient = new FirebaseClient(credential); // FirebaseClient.anonymous() is also available
var path = ... // Full path to your database location with .json appended
// GET
var response = await fbClient.get(path);
// DELETE
await fbClient.delete(path);
...
}
... however it does not show how to get the credential. The googleapis package shows how to get the credentials:
final _credentials = new ServiceAccountCredentials.fromJson(r'''
{
"private_key_id": ...,
"private_key": ...,
"client_email": ...,
"client_id": ...,
"type": "service_account"
}
''');
... however this object is not a string and it is not written anywhere how to transform this into what the FirebaseClient class expects (toString() does not work). There is a Github issue on the firebase package on how to get this credentials but it is still unanswered.
I find it hard to believe that there is no information available online, that I could find, on how to write proper integration tests for Firebase Firestore.
Considerations:
I have no interest mocking Firestore because my logic is complex and I want to test each edge-case against the real database.
I have no interest using Flutter Driver because the tests are installed like normal apps in the phone and that takes time during development and it is not as straight-forward to debug as regular unit tests. Android Studio has very good test debug tools.
How should I write integration tests that access Firebase Firestore?
For others who were brought here, as I was a few hours ago, by a search for ways to create that mysterious credential object when using the dart VM with Firebase, the following is my solution which works for accessing Firestore, at least.
The work is done by the Credentials class: (implemented below)
import 'package:firebase/firebase_io.dart';
String credential = await Credentials.fetch();
FirebaseClient fbClient = new FirebaseClient(credential);
The Credentials class creates the credential, using data from the Firebase Service Account private key (Firebase Console > [Project] > Settings > Project Settings > Service Accounts, then download the private key).
import 'dart:io';
import 'dart:convert';
import "package:googleapis_auth/auth_io.dart";
import 'package:http/http.dart' as http;
class Credentials {
/// Returns a credential string to be used in the constructor
/// of [FirebaseClient].
static Future<String> fetch() async {
Map<String, dynamic> pk = await getPrivateKey();
// Fields from Firebase private key
var accountCredentials = ServiceAccountCredentials.fromJson({
"private_key_id": pk['private_key_id'],
"private_key": pk['private_key'],
"client_email": pk['client_email'],
"client_id": pk['client_id'],
"type": "service_account",
});
// Define the required scopes.
// see https://firebase.google.com/docs/firestore/use-rest-api#working_with_google_identity_oauth_20_tokens
var scopes = [
"https://www.googleapis.com/auth/datastore",
];
var client = new http.Client();
AccessCredentials credentials =
await obtainAccessCredentialsViaServiceAccount(
accountCredentials, scopes, client);
client.close();
return credentials.accessToken.data;
}
static Future<Map<String, dynamic>> getPrivateKey() async {
String jsonString =
await File('/Path/to/firebase_private_key/keyfile.json')
.readAsString();
return json.decode(jsonString);
}
}
Adding the scope below allowed me to access firebase realtime
'https://www.googleapis.com/auth/userinfo.email'
According to https://developers.google.com/identity/protocols/oauth2/scopes it says that that scope is allowed to 'View your email address'. This doesn't seem quite right but it made it work.
(if anyone can help me understand this please add to the answer).

Firebase 3.0 + Ember 2.0: The Torii adapter must implement `open` for a session to be opened

I'm having an issue with facebook authentication with the torii adapter, the error being: 'The Torii adapter must implement open for a session to be opened'.
I've visited many tutorials, and tried all presented methods, but most of them are usually old ember code and none of them seem to actually work.
Current state: I am able to login, I get the facebook popup and I can authorize.
Using fiddler, I can also see the response from the API containing a JSON response with all credentials from the user I authenticated with.
In the firebase console, I can see the authorized user, reset its password, deny access,...
All this leads me to believe that it's 'merely' a front-end issue where I can't seem to establish a proper 'session' to work with.
My end goal would be to pick up the relevant user data and transfer it to my firebase backend as a 'user' entry, allowing for quick registration for site visitors, but I'll be more than glad to have an active session so I can work out the rest on my own.
As a front-end rookie (I normally code C#), Ember may not have been the best choice to get the hang it, but I'm this far now, I'm not about to let it all slide and pick up a different framework.
My code:
config/environment.js
firebase: {
apiKey: 'xxxxxxx',
authDomain: 'myappname.firebaseapp.com',
databaseURL: 'https://myappname.firebaseio.com',
storageBucket: 'myappname.appspot.com',
messagingSenderId: '1234567890'
},
torii: {
sessionServiceName: 'session'
}
torii-adapters/application.js (I've changed this so much, I can't even remember what the original code was, because none of what I change/add/delete here seems to do anything at all.)
import Ember from 'ember';
import ToriiFirebaseAdapter from 'emberfire/torii-adapters/firebase';
export default ToriiFirebaseAdapter.extend({
firebase: Ember.inject.service(),
});
routes/application.js
import Ember from 'ember';
export default Ember.Route.extend({
beforeModel: function() {
return this.get('session').fetch().catch(function() {
});
},
actions:{
login: function(provider) {
this.get('session').open('firebase', {
provider: provider,
}).then(function(data) {
console.log(data.currentUser);
});
},
logout: function() {
this.get('session').close().then(function() {
this.transitionTo('application');
}.bind(this));
}
}
});
application.hbs
<div class="container">
{{partial 'navbar'}}
<a {{action "signIn" "facebook"}} class="btn">{{fa-icon "facebook"}}</a>
<a {{action "signIn" "twitter"}} class="btn">{{fa-icon "twitter"}}</a>
<a {{action "signIn" "github"}} class="btn">{{fa-icon "github"}}</a>
<a {{action "signIn" "google"}} class="btn">{{fa-icon "google"}}</a>
{{outlet}}
</div>
EDIT 1
Above code is giving me alot more errors after restarting ember server. Is this the cause of my troubles ? All the changes that seemingly didn't change a thing, weren't registered until after a server restart ? If that's the case, I may have passed the correct solution about a hundred times already...
EDIT 2
Changed the code to reflect the actual issue. The previous code was screwed beyond measure, but I never realized it because it didn't pick up until after a server restart.
EDIT 3
Found and tried this, to no avail: https://stackoverflow.com/a/32079863/4309050
This is Lorem Ipsum Dolor's answer, but updated for Ember 3.16+
// Inside routes/application.js
import Route from '#ember/routing/route';
import { inject as service } from '#ember/service';
export default class ApplicationRoute Route {
#service session;
async beforeModel() {
try {
return this.session.fetch();
} catch {}
}
}
Note that in Ember 3.16+, it is not recommended to add actions to your Route.
Instead, you can add them to a Controller or Component context:
import Component from '#glimmer/component';
import { inject as service } from '#ember/service';
import { action } from '#ember/object';
export default class LoginLogout extends Component {
#service session;
#service router;
#action
async login(provider) {
let data = await this.session.open('firebase', { provider });
console.log(data.currentUser);
}
#action
async logout() {
await this.session.close();
this.router.transitionTo('application');
}
}
Note the addition of the router service. The Router service is the way we interact with routing anywhere in our apps.
import ToriiFirebaseAdapter from 'emberfire/torii-adapters/firebase';
export default class MyAdapter extends ToriiFirebaseAdapter {
}
I had the same issue and I noticed that my torii-adapters/application.js located under the pods structure (because I use it). So I moved the torii-adapters folder to app folder and everything started to work.
NOTE: for Ember 3.16+ apps, here is the same code, but with updated syntax / patterns: https://stackoverflow.com/a/62500685/356849
The below is for Ember < 3.16, even though the code would work as 3.16+ as fully backwards compatible, but it's not always fun to write older code.
Try to inject service inside your application route and move the beforeModel outside of the actions hash,
// Inside routes/application.js
export default Ember.Route.extend({
session: Ember.inject.service(), // (1)
beforeModel: function() {
return this.get('session').fetch().catch(function() {});
},
actions:{
login: function(provider) {
this.get('session').open('firebase', {
provider: provider,
}).then(function(data) {
console.log(data.currentUser);
});
},
logout: function() {
this.get('session').close().then(function() {
this.transitionTo('application');
}.bind(this));
}
}
});
I have completed the same thing (Firebase Torii Auth) yesterday, try to follow the guide carefully. The only thing missing from the guide is to inject session service manually.
Still remember the session you declared inside environment.js file? You have to inject it to make it available
session: Ember.inject.service(), // (1)
https://github.com/firebase/emberfire/blob/master/docs/guide/authentication.md
Inside my ToriiFirebaesAdapter,
import ToriiFirebaseAdapter from 'emberfire/torii-adapters/firebase';
export default ToriiFirebaseAdapter.extend({
});

How to use flux with meteor

I am starting on a new project and I want to keep my code as structured as possible. I was planning on using the flux pattern for the front-end but it feels like the event driven process that flux follows, goes against the grain of the reactionary way that meteor handles data and view updates.
Here is an example of what one of my stores might look like using flux and meteor
import { EventEmitter } from 'events'
import appDispatcher from '../Dispatcher'
import Meteor from 'meteor/meteor'
class TaskStore extends EventEmitter {
constructor() {
super();
this.tasks = [];
Meteor.subscribe('tasks');
}
addTask(task) {
Meteor.addTask(task)
this.tasks = Meteor.findAll();
}
getTasks() {
return this.tasks
}
}
const taskStore = new TaskStore();
appDispatcher.register((payload) => {
switch (payload.actionName) {
case 'CLICK':
taskStore.addTask(payload.newItem.action);
taskStore.emit('ADD_TASK');
break;
default:
}
});
export default taskStore
It's pretty straight forward, the store responds to the dispatcher by adding a task to the mongo database, then updates the local model with the data from the database and emits a change event. The view would respond by calling the getTasks() method and updating the state.
This works, but it doesn't feel very reactionary. I need a separate method exposed for finding all the tasks for example, where as in the documentation for react views that meteor provides, they have their own special function that wraps components and updates the props of the component whenever the data changes
export default createContainer(() => {
Meteor.subscribe('tasks');
return {
tasks: Tasks.find({}, { sort: { createdAt: -1 } }).fetch(),
incompleteCount: Tasks.find({ checked: { $ne: true } }).count()
})
This seems to be the way that meteor was designed. Views react to data changes and update in real time across all platforms, and I'm just not certain if my implementation of the flux pattern is the best way to stay true to that design, or if I should even bother trying to stay true to that design at all.
As a disclaimer, I'm still extremely new to both the flux pattern, and the Meteor framework.

Resources