Is there a way to use Firebase with a ShareExtension without AppGroups - swift

I'm setting up a shareExtension in iOS and want to use the FirebaseSDK to upload data direct instead of using AppGroups. This works as expected, but after 1 hour the UserToken get's invalidated and i can't reach the Firestore Backend anymore.
I'm using the FirebaseSDK (6.2.0) and enabled Keychain sharing to access the current signedIn User. I have the same Google-Plist in the MainApp and the shareExtension. The data gets also uploaded correctly from the shareExtension and was also updated via the snapshotListener in the MainApp.
Relevant code in the MainApp
lazy var db = Firestore.firestore()
//TEAMID form the Apple Developer Portal
let accessGroup = "TEAMID.de.debug.fireAuthExample"
override func viewDidLoad() {
super.viewDidLoad()
do {
try Auth.auth().useUserAccessGroup("\(accessGroup)")
} catch let error as NSError {
print("Error changing user access group: %#", error)
}
guard let user = Auth.auth().currentUser else {
self.statusLabel.text = "user get's lost"
return
}
statusLabel.text = "UserID: \(user.uid)"
// Do any additional setup after loading the view.
db.collection("DummyCollection").addSnapshotListener { (querySnapshot, error) in
if let err = error {
print(err.localizedDescription)
}
guard let snapshot = querySnapshot else {
return
}
DispatchQueue.main.async {
self.dbCountLabel.text = "\(snapshot.count)"
}
}
}
func signIN(){
// https://firebase.google.com/docs/auth/ios/single-sign-on
do {
try Auth.auth().useUserAccessGroup("\(accessGroup)")
} catch let error as NSError {
print("Error changing user access group: %#", error)
}
Auth.auth().signInAnonymously { (result, error) in
if let err = error{
print(err.localizedDescription)
return
}
print("UserID: \(Auth.auth().currentUser!.uid)")
}
}
}
}
Code in the shareExtension:
override func viewDidLoad() {
super.viewDidLoad()
if FirebaseApp.app() == nil {
FirebaseApp.configure()
}
do {
try Auth.auth().useUserAccessGroup(accessGroup)
} catch let error as NSError {
print("Error changing user access group: %#", error)
}
tempUser = Auth.auth().currentUser
if tempUser != nil {
userIDLabel.text = "UserID: \(tempUser!.uid)"
doneButton.isEnabled = true
db.collection("DummyCollection").addSnapshotListener { (querySnapshot, error) in
if let err = error {
print(err.localizedDescription)
}
guard let snapshot = querySnapshot else {
return
}
DispatchQueue.main.async {
self.dataCountLabel.text = "\(snapshot.count)"
}
}
} else {
// No user exists in the access group
self.navigationItem.title = "No User"
}
}
I expect that this should be possible, but the Token gets somehow invalid in the MainApp and i could not reach the Firestore backend.
6.2.0 - [Firebase/Auth][I-AUT000003] Token auto-refresh re-scheduled in 01:00 because of error on previous refresh attempt.
6.2.0 - [Firebase/Firestore][I-FST000001] Could not reach Cloud Firestore backend. Connection failed 1 times. Most recent error: An internal error has occurred, print and inspect the error details for more information.

Answering my own question: This should be fixed in the next release (Firebase 6.4.0) Details can be found in this PR 3239.

Related

Apple Watch enableBackgroundDelivery is not working at all

let washingEvent = HKCategoryType.categoryType(forIdentifier: .handwashingEvent)!
healthStore.execute(query)
healthStore.enableBackgroundDelivery(
for: washingEvent,
frequency: .immediate,
withCompletion: { succeeded, error in
print("enableBackgroundDelivery")
if let unwrappedError = error {
print("could not enable background delivery: \(unwrappedError)")
}
if succeeded {
print("background delivery enabled")
}
print(error)
//here I send data to my server .
return
}
// Background delivery is enabled
}
)
let query2 = HKObserverQuery(
sampleType: washingEvent,
predicate: nil,
updateHandler: { query, completionHandler, error in
print("error in \(error)")
defer {
completionHandler()
}
guard error != nil else {
return
}
// TODO
})
healthStore.execute(query2)
I added washingEvent but after app is gone background and I washed with Apple Watch well, but not receiving any from my server. it seems it didn't come to call back of enableBackgroundDelivery or erverQueHKry ...
how can I get Information when background ?

Upload Image error: User is not authenticated, please authenticate using Firebase Authentication and try again

My application is logged in via anonymous authentication, I have a class on SwiftUI for uploading a user's photo from an iPhone, however, when uploading a photo to Firebase storage, I get the error:
User is not authenticated, please authenticate using Firebase
Authentication and try again.
Here's my code:
AuthViewModel.kt:
class AuthViewModel: NSObject, ObservableObject {
#Published var userID = ""
func login() {
Auth.auth().signInAnonymously { (res, err) in
if err != nil {
print(err!.localizedDescription)
return
}
print("Success = \(res!.user.uid)")
self.userID = res!.user.uid
}
}
}
UserViewModel:
func setCurrentUserImage(profileImage: UIImage) {
guard let imageData = profileImage.jpegData(compressionQuality: 0.3) else { return }
let filename = NSUUID().uuidString
let storageRef = Storage.storage().reference().child("UserPhotos").child("\(self.currentUser)/\(filename)")
storageRef.putData(imageData, metadata: nil) { _, error in
if let error = error {
print("Upload Image error: \(error.localizedDescription)")
return
}
storageRef.downloadURL { url, _ in
guard let profileImageURL = url?.absoluteString else { return }
let docRef = self.db.collection("users").document(self.currentUser)
docRef.getDocument(source: .cache) { (snap, err) in
guard let curUser = snap else { return }
curUser.reference.updateData(["userImage" : profileImageURL])
}
}
}
}
Firebase storage security rules:
match /{allPaths=**} {
allow read, write;
}

how to fix app freeze after dispatchsemaphore call

I was reading up on this question about app freezes and semaphores and I tried to implement the answer into my code, but it still freezes my app despite calling the UI work on the main thread. My goal is to stop the app from freezing once all the entries are called and have the UI work continue like normal.
This is the alert action I have in the deletion method so far:
let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { (deletion) in
let semaphore = DispatchSemaphore(value: 0)
self.deleteButton.isHidden = true
self.loadingToDelete.alpha = 1
self.loadingToDelete.startAnimating()
DispatchQueue.global(qos: .userInitiated).async {
self.db.collection("student_users/\(user.uid)/events_bought").getDocuments { (querySnapshot, error) in
guard error == nil else {
print("The docs couldn't be retrieved for deletion.")
return
}
guard querySnapshot?.isEmpty == false else {
print("The user being deleted has no events purchased.")
return
}
for document in querySnapshot!.documents {
let docID = document.documentID
self.db.collection("student_users/\(user.uid)/events_bought/\(docID)/guests").getDocuments { (querySnap, error) in
guard querySnap?.isEmpty == false else {
print("The user being deleted has no guests with his purchases.")
return
}
for doc in querySnap!.documents {
let guest = doc.documentID
self.db.document("student_users/\(user.uid)/events_bought/\(docID)/guests/\(guest)").delete { (error) in
guard error == nil else {
print("Error deleting guests while deleting user.")
return
}
print("Guests deleted while deleting user!")
semaphore.signal()
}
semaphore.wait()
}
}
}
}
self.db.collection("student_users/\(user.uid)/events_bought").getDocuments { (querySnapshot, error) in
guard error == nil else {
print("There was an error retrieving docs for user deletion.")
return
}
guard querySnapshot?.isEmpty == false else {
return
}
for document in querySnapshot!.documents {
let docID = document.documentID
self.db.document("student_users/\(user.uid)/events_bought/\(docID)").delete { (err) in
guard err == nil else {
print("There was an error deleting the the purchased events for the user being deleted.")
return
}
print("Purchases have been deleted for deleted user!")
semaphore.signal()
}
semaphore.wait()
}
}
self.db.document("student_users/\(user.uid)").delete(completion: { (error) in
guard error == nil else {
print("There was an error deleting the user document.")
return
}
print("User doc deleted!")
semaphore.signal()
})
semaphore.wait()
user.delete(completion: { (error) in
guard error == nil else {
print("There was an error deleting user from the system.")
return
}
print("User Deleted.")
semaphore.signal()
})
semaphore.wait()
DispatchQueue.main.async {
self.loadingToDelete.stopAnimating()
self.performSegue(withIdentifier: Constants.Segues.studentUserDeletedAccount, sender: self)
}
}
}
So this actually deletes everything cleanly with no residual data in the Firestore database, which is what I wanted to happen all along, the only issue is that the app freezes. I thought that the answer in the question I linked above would work in my case, but it didn't.
Also to mention, I've had suggestions of using Cloud Functions for this issue but my app has two types of users with different logic and syntax in the deletion process so I couldn't just use a simple auth().onDelete() in Cloud Functions and clean up residue. Even if I could, it would be the same issue I'm facing here but just on the server side, trying to order the tasks correctly, which in my opinion is repetitive and not the most sensible thing to do at this point.
Any other suggestions to overcome this issue? Thanks in advance.
EDIT Since semaphores are not the way to go, I resorted to this :
let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { (deletion) in
self.deleteButton.isHidden = true
self.loadingToDelete.alpha = 1
self.loadingToDelete.startAnimating()
self.db.collection("student_users/\(user.uid)/events_bought").getDocuments { (querySnapshot, error) in
guard error == nil else {
print("The docs couldn't be retrieved for deletion.")
return
}
guard querySnapshot?.isEmpty == false else {
print("The user being deleted has no events purchased.")
return
}
for document in querySnapshot!.documents {
let docID = document.documentID
self.db.collection("student_users/\(user.uid)/events_bought/\(docID)/guests").getDocuments { (querySnap, error) in
guard querySnap?.isEmpty == false else {
print("The user being deleted has no guests with his purchases.")
return
}
let group = DispatchGroup()
for doc in querySnap!.documents {
let guest = doc.documentID
group.enter()
self.db.document("student_users/\(user.uid)/events_bought/\(docID)/guests/\(guest)").delete { (error) in
guard error == nil else {
print("Error deleting guests while deleting user.")
return
}
print("Guests deleted while deleting user!")
group.leave()
}
}
}
}
}
self.db.collection("student_users/\(user.uid)/events_bought").getDocuments { (querySnapshot, error) in
guard error == nil else {
print("There was an error retrieving docs for user deletion.")
return
}
guard querySnapshot?.isEmpty == false else {
return
}
let group = DispatchGroup()
for document in querySnapshot!.documents {
let docID = document.documentID
group.enter()
self.db.document("student_users/\(user.uid)/events_bought/\(docID)").delete { (err) in
guard err == nil else {
print("There was an error deleting the the purchased events for the user being deleted.")
return
}
print("Purchases have been deleted for deleted user!")
group.leave()
}
}
}
self.db.collection("student_users").whereField("userID", isEqualTo: user.uid).getDocuments { (querySnapshot, error) in
guard error == nil else {
print("There was an error deleting the user document.")
return
}
guard querySnapshot?.isEmpty == false else {
return
}
let group = DispatchGroup()
for document in querySnapshot!.documents {
let docID = document.documentID
group.enter()
self.db.document("student_users/\(docID)").delete { (err) in
guard err == nil else {
return
}
print("User doc deleted!")
group.leave()
}
}
}
let group = DispatchGroup()
group.enter()
user.delete(completion: { (error) in
guard error == nil else {
print("There was an error deleting user from the system.")
return
}
print("User Deleted.")
group.leave()
})
group.notify(queue: .main) {
self.loadingToDelete.stopAnimating()
self.performSegue(withIdentifier: Constants.Segues.studentUserDeletedAccount, sender: self)
}
}
This still leaves residual data and does not execute the tasks in order. Any other suggestions?
Let me give you some ideas because I think your solution should incorporate some or all of these. First is how dispatch groups work and how you can nest them to execute blocks of async tasks in order:
func deleteUser(completion: #escaping (_ done: Bool) -> Void) {
// put UI into loading state
db.collection("someCollection").getDocuments { (snapshot, error) in
if let snapshot = snapshot {
if snapshot.isEmpty {
completion(true) // no errors, nothing to delete
} else {
let dispatchGroup = DispatchGroup() // instantiate the group outside the loop
var hasErrors = false
for doc in snapshot.documents {
dispatchGroup.enter() // enter on every iteration
db.document("someDocument").delete { (error) in
if let error = error {
print(error)
hasErrors = true
}
dispatchGroup.leave() // leave on every iteration regardless of outcome
}
}
dispatchGroup.notify(queue: .main) {
if hasErrors {
completion(false) // failed to delete
} else {
// execute next task and repeat
}
}
}
} else {
if let error = error {
print(error)
completion(false) // failed to delete
}
}
}
}
deleteUser { (done) in
if done {
// segue to next view controller
} else {
// retry or alert user
}
}
The example above is the basics of how dispatch group can work for you. When you leave the group the same number of times you've entered it, the completion handler is called. This example does not have any recursion and doesn't check if everything was actually deleted. Here is an example of how you could add some of that:
func deleteUser(completion: #escaping (_ done: Bool) -> Void) {
var retries = 0
func task() {
db.collection("someCollection").getDocuments { (snapshot, error) in
if let snapshot = snapshot {
if snapshot.isEmpty {
completion(true) // done, nothing left to delete
} else {
// delete the documents using a dispatch group or a Firestore batch delete
task() // call task again when this finishes
// because this function only exits when there is nothing left to delete
// or there have been too many failed attempts
}
} else {
if let error = error {
print(error)
}
retries += 1 // increment retries
run() // retry
}
}
}
func run() {
guard retries < 5 else {
completion(false) // 5 failed attempts, exit function
return
}
if retries == 0 {
task()
} else { // the more failures, the longer we wait until retrying
DispatchQueue.main.asyncAfter(deadline: .now() + Double(retries)) {
task()
}
}
}
run()
}
This doesn't answer your question directly but it should help you with the task overall. You can also forego some of the looping and deleting and do it all inside a Firestore batch operation, which comes with its own completion handler. There are lots of ways to tackle this but these are some things I'd consider.

Download firebase storage url not working

I have an image that is uploaded to the Firebase Storage but I can not get the downloadURL of this to save it in my database.
I already read a lot of posts on StackOverflow but there was nothing working out for me. The image is loaded into the storage but the error says that it is not existing.
let image = self.selectedImage
let imageData = UIImageJPEGRepresentation(image, 0.1)
let storageRef = Storage.storage().reference().child(uid)
storageRef.putData(imageData, metadata: nil) // i know that i can use a completion here but i left it for now
storageRef.downloadURL { url, error in
if let error = error {
print(error)
} else {
// do sth. else
}
}
These are the errors that I get:
FIRStorageErrorDomain Code=-13010 "Object W002MjRvi0d8JfVwImUJhH0ph2O2 does not exist."UserInfo={object=W002MjRvi0d8JfVwImUJhH0ph2O2,
ResponseBody={
"error": {
"code": 404,
"message": "Not Found. Could not get object"
}
}
ResponseErrorDomain=com.google.HTTPStatus, ResponseErrorCode=404}
Please check Storage Rules
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth!=null;
}
}
}
In Your ViewController
import Firebase
import FirebaseAuth
import ImageIO
var imgData: NSData = NSData(data: UIImageJPEGRepresentation((self.img_Photo?.image)!, 0.5)!)
self.uploadProfileImageToFirebase(data: imgData)
func uploadProfileImageToFirebase(data:NSData){
guard let userID = Auth.auth().currentUser?.uid else {
return
}
let storageRef = Storage.storage().reference().child(“Images”).child(userID)
if data != nil {
storageRef.putData(data as Data, metadata: nil, completion: { (metadata, error) in
if(error != nil){
print(error)
return
}
// Fetch the download URL
storageRef.downloadURL { url, error in
if let error = error {
// Handle any errors
if(error != nil){
print(error)
return
}
} else {
// Get the download URL for 'images/stars.jpg'
let urlStr:String = (url?.absoluteString) ?? ""
}
}
})
}
}

Check Firebase Storage before creating a new profile Image URL

I am having a bit of an issue checking Firebase Storage when a user logs into my app. When a user logs in an image is created and stored successfully in Firebase database and storage, but every time a user logs out and logs back into the app the profileImage file (not the child value url string) is duplicated in Firebase Storage as a new and separate image file.
I would like the app when logging in to check the Firebase storage for the profileImage file, and if it exists then do not re-create that same image in storage. I understand the logic of checking Firebase for image and if does not exist, than create new image. I am just having some trouble with the syntax. Thanks for help in advance!
// login user with Facebook
func loginWithFacebook() {
let accessToken = FBSDKAccessToken.current()
guard let accessTokenString = accessToken?.tokenString else { return }
let credentials = FIRFacebookAuthProvider.credential(withAccessToken: accessTokenString)
FIRAuth.auth()?.signIn(with: credentials, completion: { (user, error) in
if error != nil {
print("Something went wrong with our FB user: ", error ?? "")
return
}
guard let uid = user?.uid else {
return
}
let imageName = NSUUID().uuidString
let storageRef = FIRStorage.storage().reference().child("profile_images").child("\(imageName).png")
let photoUrl = user?.photoURL
// check to see if current user already has stored image with URL
if FIRStorage.storage().reference().child("profile_images").child("\(imageName).png") == nil {
if let imageData = NSData(contentsOf: photoUrl!) {
storageRef.put(imageData as Data, metadata:nil) {
(metadata, error) in
if error != nil {
print(error!)
return
} else {
if let profileImageUrl = metadata?.downloadURL()?.absoluteString {
let values = ["name": user!.displayName!, "email": user!.email!, "profileImageUrl": profileImageUrl]
self.registerUserWithUID(uid: uid, values: values as [String : AnyObject])
}
}
}
}
} else {
print("image already exists")
}
print("Successfully logged in with our user: ", user ?? "")
self.delegate?.finishLoggingIn()
})
private func registerUserWithUID(uid: String, values: [String: AnyObject]) {
// create items in database upon creating user ---------------
let ref = FIRDatabase.database().reference()
let usersReference = ref.child("users").child(uid)
usersReference.updateChildValues(values, withCompletionBlock: { (err, ref) in
if err != nil {
print(err!)
return
}
print("user has been saved to Firebase database")
})
}
I think the issue is with the "if" condition. You are referencing the location, but not checking with the actual data in that location.
You can implement that using .observerSingleEventOf().
private func checkUser (imageName:String, _ completionHandler: #escaping(Bool) -> Void)
{
let userExists = FIRStorage.storage().reference().child("profile_images")
userExists.observeSingleEvent(of: .value, with:{(snapshot) in
if snapshot.hasChild(imageName){
print ("Image already exists")
completionHandler(false)
}
else
{
print ("Image does not exist")
print ("store the image")
}
})
}
`