Resolver SwiftUI Causing Crash - What Order Do Things Get Called? - firebase

I am working on SwiftUI and using Resolver for Dependency Injection. As a backend I am using Firebase. I've created an AuthSession file that handles all of my user authentication stuff. In the project I also have a number of other repositories that populate data throughout the app. In AuthSession I am creating properties for each repository so that I can start and stop Firestore Listeners on login and logout. In a couple of these repositories I want to access AuthSession through #InjectedObject so that when a user logs in I can be notified and can get updates via Combine. My issues is that when I start the app, it crashes with an odd Firebase error.
AuthSession.swift
class AuthSession: ObservableObject {
let db = Firestore.firestore()
var offerRepository: OfferRepository = Resolver.resolve()
var handle: AuthStateDidChangeListenerHandle?
#Published var currentUser: User?
#Published var loggedIn = false
#Published var currentUserUid = ""
// Combine Cancellable
private var cancellables = Set<AnyCancellable>()
// Intitalizer
init() {
}
func listen() {
print("AuthSession - listen called")
// Monitor Authentication chagnes using Firebase Auth.
handle = Auth.auth().addStateDidChangeListener{ (auth, user) in
// Check to see if a user is returned from a sign in or sign up event.
if let user = user {
// Set loggedIn to true. This will also be set when a new User is created in SignUpView.
print("User Exists.")
self.loggedIn = true
self.currentUserUid = user.uid
self.currentUser = user
} else {
print("Not logged in")
}
}
}
}
Below is OfferRepository. When the line below is added it crashes. If the line is removed it does not crash. I'm not sure why. The Combine code is not included.
Line causing the crash.
#InjectedObject var authSession: AuthSession
OfferRepository.swift
class OfferRepository: ObservableObject {
let db = Firestore.firestore()
private var snapshotListener: ListenerRegistration?
#InjectedObject var authSession: AuthSession
#Published var offers = [Offer]()
private var cancellables = Set<AnyCancellable>()
init() {
startSnapshotListener()
}
func startSnapshotListener() {
if snapshotListener == nil {
self.snapshotListener = db.collection(FirestoreCollection.offers).order(by: "created", descending: true).addSnapshotListener { (querySnapshot, error) in
if let error = error {
print("Error getting documents: \(error)")
} else {
guard let documents = querySnapshot?.documents else {
print("No Offers.")
return
}
self.offers = documents.compactMap { offer in
do {
return try offer.data(as: Offer.self)
} catch {
print(error)
}
return nil
}
}
}
}
}
}
For reference here is my AppDelegate+Registering file.
extension Resolver: ResolverRegistering {
public static func registerAllServices() {
register { AuthSession() }.scope(.application)
register { OfferRepository() as OfferRepository }.scope(.application)
}
}
The app crashed on the line below from the Firestore package.
- (NSString *)keyForDatabase:(NSString *)database {
return [NSString stringWithFormat:#"%#|%#", self.app.name, database];
}
Thread 1: EXC_BAD_ACCESS (code=2, address=0x16d317ff8)
While I can start and stop listeners from login and logout views, I'd prefer to keep this in the AuthSession file. Is there a way around this?

#InjectedObject is intended to be used to inject ObservableObjects into SwiftUI views - see the docs: https://github.com/hmlongco/Resolver#property-wrappers
As you want to reference the AuthenticationService inside your repositories (which are ObservableObjects, you should use #Injected instead.
Here is a snippet from one of my apps:
public class ArtifactRepository: ObservableObject {
// MARK: - Dependencies
#Injected var db: Firestore
#Injected var authenticationService: AuthenticationService
// MARK: - Publishers
#Published public var artifacts = [Artifact]()
// MARK: - Private attributes
private var statusFilter: Status
private var userId: String = "unknown"
private var listenerRegistration: ListenerRegistration?
private var cancellables = Set<AnyCancellable>()
let logger = Logger(subsystem: "dev.peterfriese.App", category: "persistence")
public init(statusFilter: Status = .inbox, liveSync: Bool = true) {
// filtering
self.statusFilter = statusFilter
// observe user ID
authenticationService.$user
.compactMap { user in
user?.uid
}
.assign(to: \.userId, on: self)
.store(in: &cancellables)
// if live sync is on, (re)load data when user changes
if liveSync {
authenticationService.$user
.receive(on: DispatchQueue.main)
.sink { [weak self] user in
if self?.listenerRegistration != nil {
self?.unsubscribe()
self?.subscribe()
}
}
.store(in: &cancellables)
}
}
deinit {
unsubscribe()
}
public func unsubscribe() {
if listenerRegistration != nil {
listenerRegistration?.remove()
listenerRegistration = nil
}
}
public func subscribe() {
if listenerRegistration == nil {
var query = db.collection("artifacts")
.whereField("userId", isEqualTo: self.userId)
if (statusFilter != .all) {
query = query.whereField("status", isEqualTo: statusFilter.rawValue)
}
listenerRegistration = query.order(by: "dateAdded", descending: true)
.addSnapshotListener { [weak self] (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
self?.logger.debug("No documents")
return
}
self?.logger.debug("Mapping \(documents.count) documents")
self?.artifacts = documents.compactMap { queryDocumentSnapshot in
try? queryDocumentSnapshot.data(as: Artifact.self)
}
}
}
}
}
public class AuthenticationService: ObservableObject {
private let logger = Logger(subsystem: "dev.peterfriese.App", category: "authentication")
#Published public var user: User?
private var handle: AuthStateDidChangeListenerHandle?
public init() {
setupKeychainSharing()
registerStateListener()
}
public func signIn() {
if Auth.auth().currentUser == nil {
Auth.auth().signInAnonymously()
}
}
public func signOut() {
do {
try Auth.auth().signOut()
}
catch {
print("error when trying to sign out: \(error.localizedDescription)")
}
}
private let accessGroup = "XXXXXXX.dev.peterfriese.App"
private func setupKeychainSharing() {
do {
let auth = Auth.auth()
auth.shareAuthStateAcrossDevices = true
try auth.useUserAccessGroup(accessGroup)
}
catch let error as NSError {
print("Error changing user access group: %#", error)
}
}
private func registerStateListener() {
if handle == nil {
handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
self.user = user
if let user = user {
if user.isAnonymous {
self.logger.debug("User signed in anonymously with user ID \(user.uid).")
}
else {
self.logger.debug("User signed in with user ID \(user.uid). Email: \(user.email ?? "(empty)"), display name: [\(user.displayName ?? "(empty)")]")
}
}
else {
self.logger.debug("User signed out.")
self.signIn()
}
})
}
}
}

Related

SwiftUI return Bool from authorizationController async

How could I return a Bool from this sign in with apple function that uses the authorizationController? I have been looking at withCheckedThrowingContinuation but with no luck. I have also seen some examples of using completion handlers but I would like to use async.
func signInWithApple(withState state: SignInState) async -> Bool {
let request = signInWithAppleRequest(withState: state)
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.performRequests()
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
//...
let credential = OAuthProvider.credential(withProviderID: "apple.com", idToken: idTokenString, rawNonce: nonce)
switch state {
case .signIn:
Task {
authenticationState = .authenticating
do {
let result = try await Auth.auth().signIn(with: credential)
if result.additionalUserInfo?.isNewUser == true {
self.isNewUser = true
}
return true
} catch {
self.authenticationState = .unauthenticated
print(error.localizedDescription)
return false
}
}
case .reauth:
Task {
if let user = Auth.auth().currentUser {
do {
try await user.reauthenticate(with: credential)
return true
} catch {
print(error.localizedDescription)
return false
}
}
}
}
}
}
I don't have all your types, so here is a simple example of a continuation which at least compiles.
The class must inherit from NSObject and adopt ASAuthorizationControllerDelegate.
The continuation is set in the same scope where the request is executed.
The most important rule is: The continuation must be resumed exactly once
import AuthenticationServices
class Authorization: NSObject, ASAuthorizationControllerDelegate {
private var continuation : CheckedContinuation<ASAuthorization,Error>?
func signInWithApple() async throws -> ASAuthorization {
return try await withCheckedThrowingContinuation { continuation in
self.continuation = continuation
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.performRequests()
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
continuation?.resume(returning: authorization)
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
continuation?.resume(throwing: error)
}
}

I Have To Restart A Firestore SnapshotListener And I Don't Think That's Right

UPDATED: I have a project that is using Firebase Firestore. I have snapshot listeners set up to my model objects. I start the snapshot listeners when the app starts. My understanding is that if you start a snapshot listener once that's all you need.
My issue is that when I create a new object in the app I must restart the snapshot listener in order for it to see the changes. I am wondering if I am making a mistake somewhere. Here are the files I think are important. If anything else is needed please let me know.
Main
Main where I create the environment objects for the project and start the UserRepository snapshot listener.
#main
struct TheExchangeApp: App {
// #EnvironmentObjects
#StateObject private var userRepository = UserRepository()
#StateObject private var authListener = AuthSession(userRepository: UserRepository())
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(userRepository)
.environmentObject(authListener)
}
}
}
UserRepository.swift
This is the user repository were I create the snapshot listener.
class UserRepository: ObservableObject {
// Access to Firestore Database
let db = Firestore.firestore()
private var snapshotListener: ListenerRegistraion?
#Published var users = [User]()
init() {
startSnapshotListener()
}
func startSnapshotListener() {
// Add a SnapshotListener to the User Collection.
if snapshotListner == nil {
db.collection("users").addSnapshotListener { (querySnapshot, error) in
if let error = error {
print("Error getting documents: \(error)")
} else {
guard let documents = querySnapshot?.documents else {
print("No Users.")
return
}
self.users = documents.compactMap { user in
do {
return try user.data(as: User.self)
} catch {
print(error)
}
return nil
}
}
}
}
}
func stopSnapshotListener() {
if snapshotListener != nil {
snapshotListener?.remove()
snapshotListener = nil
}
}
func createNewUser(userUid: String, user: User) {
do {
let _ = try self.db.collection(FirestoreCollection.users).document(userUid).setData(from: user)
if snapshotListener == nil {
print("createNewUser snapshotListener is nil")
} else if snapshotListener != nil {
print("createNewUser snapshotListener is not nil")
}
} catch let error as NSError {
print("UserRepository - createNewUser Error: \(error)")
}
}
}
AuthSession
Here I start a combine publisher on the userRepository snapshot listener. The publisher is not getting an update when I create a new user in SignUpView below. If I restart the snapshot listener in UserRepository I get the published value.
class AuthSession: ObservableObject {
let db = Firestore.firestore()
var userRepository: UserRepository
#Published var currentUser: User?
private var cancellables = Set<AnyCancellable>()
init(userRepository: UserRepository) {
self.userRepository = userRepository
self.startCombine()
}
func startCombine() {
userRepository
.$users
.receive(on: RunLoop.main)
.map { users in
users
.first(where: { $0.id == self.currentUserUid})
}
.assign(to: \.currentUser, on: self)
.store(in: &cancellables)
}
}
SignUp View
Here is where I create a new user. The snapshot listener does not work unless I restart. I know because the combine publisher in AuthSession does not fire.
struct SignUpView: View {
#EnvironmentObject var authSession: AuthSession
let db = Firestore.firestore()
var body: some View {
Button(action: {
self.signUp()
}, label: {
Text("Sign Up")
})
}
func signUp() {
authSession.listen()
let newUser = User(email: myemail#email.com, name: "Hans")
self.authSession.userRepository.createNewUser(userUid: user.uid, user: newUser)
}
}
Any help here would be greatly appreciated.
I believe I've sorted out the issue. A developer I am working with changed the rules on Firestore. These new rules included the following for the User collection.
match /users/{userId} {
allow create: if request.auth.uid == userId;
allow read: if request.auth != null;
allow write: if request.auth.uid == userId;
Once I removed these rules everything works as expected. I hope this helps someone else out there.

Cannot create document in Firestore when using Sign in with Apple

In my project I'm using a trigger to create a user document in Firestore when the user signs in. And this is great - everything works perfect for Google Sign-In and Facebook Login.
Here is this trigger:
exports.createAccountDocument = functions.auth.user().onCreate(async user => {
const { uid, displayName } = user
const username = displayName;
const email = user.email || user.providerData[0].email;
const profileImageUrl = uid;
const status = "active";
const str = username;
const qwery = [];
for (let i = 0; i < str.length; ++i) {
qwery[i] = str.substring(0, i + 1).replace(/\s/g, "").toLowerCase();
}
const keywords = qwery;
const bio = "";
return await admin
.firestore()
.collection("users")
.doc(uid)
.set({ bio, email, keywords, profileImageUrl, status, uid, username })
})
For example - this is Facebook Login method:
import SwiftUI
import FBSDKLoginKit
import Firebase
struct FacebookAuthView: UIViewRepresentable {
#Binding var showAnimation: Bool
#Binding var showSheet: Bool
init(showAnimation: Binding<Bool>, showSheet: Binding<Bool>) {
self._showAnimation = showAnimation
self._showSheet = showSheet
}
func makeCoordinator() -> FacebookAuthView.Coordinator {
return FacebookAuthView.Coordinator(showAnimation: self.$showAnimation, showSheet: self.$showSheet)
}
class Coordinator: NSObject, LoginButtonDelegate {
#Binding var showAnimation: Bool
#Binding var showSheet: Bool
init(showAnimation: Binding<Bool>, showSheet: Binding<Bool>) {
self._showAnimation = showAnimation
self._showSheet = showSheet
}
func loginButton(_ loginButton: FBLoginButton, didCompleteWith result: LoginManagerLoginResult?, error: Error?) {
if let error = error {
print(error.localizedDescription)
return
}
guard let token = AccessToken.current else {
return
}
self.showAnimation = true
self.showSheet = false
let credential = FacebookAuthProvider.credential(withAccessToken: token.tokenString)
Auth.auth().signIn(with: credential) { (authResult, error) in
if let error = error, (error as NSError).code == AuthErrorCode.credentialAlreadyInUse.rawValue {
Auth.auth().signIn(with: credential) { result, error in
// continue
print("signIn result: " + authResult!.user.email!)
if let token = firebaseRegistrationPushToken {
checkUserAuthSettings(pushToken: token)
}
}
} else {
// continue
print("Facebook Sign In")
if let token = firebaseRegistrationPushToken {
checkUserAuthSettings(pushToken: token)
}
}
}
}
func loginButtonDidLogOut(_ loginButton: FBLoginButton) {
try! Auth.auth().signOut()
}
}
func makeUIView(context: UIViewRepresentableContext<FacebookAuthView>) -> FBLoginButton {
let view = FBLoginButton()
view.permissions = ["email"]
view.delegate = context.coordinator
return view
}
func updateUIView(_ uiView: FBLoginButton, context: UIViewRepresentableContext<FacebookAuthView>) { }
}
But when I try to create a document when user logs in using Swign in with Apple, this doesn't work. In the Firebase console under Firebase Authentication I can see new the user, but in Firestore, no document shows up at all.
Here is my Sign in with Apple method:
import Foundation
import SwiftUI
import AuthenticationServices
import CryptoKit
import Firebase
struct AppleAuthView: UIViewRepresentable {
#Binding var showAnimation: Bool
#Binding var showSheet: Bool
init(showAnimation: Binding<Bool>, showSheet: Binding<Bool>) {
self._showAnimation = showAnimation
self._showSheet = showSheet
}
func makeCoordinator() -> AppleAuthView.Coordinator {
return AppleAuthView.Coordinator(showAnimation: self.$showAnimation, showSheet: self.$showSheet)
}
class Coordinator: NSObject, ASAuthorizationControllerPresentationContextProviding, ASAuthorizationControllerDelegate {
#Binding var showAnimation: Bool
#Binding var showSheet: Bool
fileprivate var currentNonce: String?
init(showAnimation: Binding<Bool>, showSheet: Binding<Bool>) {
self._showAnimation = showAnimation
self._showSheet = showSheet
super.init()
}
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
let viewController = UIApplication.shared.windows.last?.rootViewController
return (viewController?.view.window!)!
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
guard let nonce = currentNonce else {
fatalError("Invalid state: A login callback was received, but no login request was sent.")
}
guard let appleIDToken = appleIDCredential.identityToken else {
print("Unable to fetch identity token")
return
}
guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
return
}
let credential = OAuthProvider.credential(withProviderID: "apple.com", idToken: idTokenString, rawNonce: nonce)
Auth.auth().signIn(with: credential) { (authResult, error) in
if let error = error {
print(error.localizedDescription)
return
}
print("Apple Sign In")
if let token = firebaseRegistrationPushToken {
checkUserAuthSettings(pushToken: token)
}
}
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
print("Sign in with Apple errored: \(error)")
}
#objc func startSignInWithAppleFlow() {
let nonce = randomNonceString()
currentNonce = nonce
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
request.nonce = sha256(nonce)
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
private func randomNonceString(length: Int = 32) -> String {
precondition(length > 0)
let charset: Array<Character> = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
var result = ""
var remainingLength = length
while remainingLength > 0 {
let randoms: [UInt8] = (0 ..< 16).map { _ in
var random: UInt8 = 0
let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random)
if errorCode != errSecSuccess {
fatalError("Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)")
}
return random
}
randoms.forEach { random in
if remainingLength == 0 {
return
}
if random < charset.count {
result.append(charset[Int(random)])
remainingLength -= 1
}
}
}
return result
}
#available(iOS 13, *)
private func sha256(_ input: String) -> String {
let inputData = Data(input.utf8)
let hashedData = SHA256.hash(data: inputData)
let hashString = hashedData.compactMap {
return String(format: "%02x", $0)
}.joined()
return hashString
}
}
func makeUIView(context: Context) -> ASAuthorizationAppleIDButton {
let button = ASAuthorizationAppleIDButton(type: .signIn, style: .black)
button.addTarget(context.coordinator,action: #selector(Coordinator.startSignInWithAppleFlow),for: .touchUpInside)
return button
}
func updateUIView(_ uiView: ASAuthorizationAppleIDButton, context: Context) {
}
}
I don't understand why the document cannot be created. In the console I can see nil.
Please help to fix this issue.
Updated.
Code below - is a simple function to creating user document in firestore in my app
func signup(username: String, email: String, password: String, imageData: Data, completed: #escaping(_ user: User) -> Void, onError: #escaping(_ errorMessage: String) -> Void) {
if !username.isEmpty && !email.isEmpty && !password.isEmpty && !imageData.isEmpty {
AuthService.signupUser(username: username, email: email, password: password, imageData: imageData, onSuccess: completed, onError: onError)
} else {
if username == "" {
errorString = "Please enter your name"
onError(errorString)
// showAlert = true
}
if email == "" {
errorString = "Please enter yor valid email"
onError(errorString)
// showAlert = true
}
if password == "" {
errorString = "Please create password"
onError(errorString)
// showAlert = true
}
if image == Image(IMAGE_USER_PLACEHOLDER) {
errorString = "Please upload your avatar"
onError(errorString)
// showAlert = true
}
}
}
and here is AuthService.signupUser method
static func signupUser(username: String, email: String, password: String, imageData: Data, onSuccess: #escaping(_ user: User) -> Void, onError: #escaping(_ errorMessage: String) -> Void) {
//Firebase.createAccount(username: username, email: email, password: password, imageData: imageData)
Auth.auth().createUser(withEmail: email, password: password) { (authData, error) in
if error != nil {
print(error!.localizedDescription)
onError(error!.localizedDescription)
return
}
guard let userId = authData?.user.uid else { return }
let storageAvatarUserId = Ref.STORAGE_AVATAR_USERID(userId: userId)
let metadata = StorageMetadata()
metadata.contentType = "image/jpg"
StorageService.saveAvatar(userId: userId, username: username, email: email, imageData: imageData, metadata: metadata, storageAvatarRef: storageAvatarUserId, onSuccess: onSuccess, onError: onError)
}
}
I know that apple sign in cannot take users image & it's ok - my cloud trigger fill this field in document users uid & in my app if user avatar = nil - it's takes universal clipart, It's ok, newer mind.
And this is User file
import Foundation
struct User: Encodable, Decodable {
var uid: String
var email: String
var profileImageUrl: String
var username: String
var bio: String
var keywords: [String]
var status: String?
}
my security rules in firebase are simple, here they are
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
This issue is - that Apple don’t get username.
Issue is solved.

Retrieve value from function with swift closure

I want to gather information about user with firebase firestore, I have in a Class a function which do that, but I want share this information with my struct ContentView.
class UserData : ObservableObject {
// Use Firebase library to configure APIs
#Published var db = Firestore.firestore()
#Published var WholeDocument : Array<String> = []
func GetAllData(completion: #escaping (Array<String>) -> ()) {
db.collection("Users").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
completion([""])
} else {
for document in querySnapshot!.documents {
//print("\(document.documentID) => \(document.data())")
self.WholeDocument.append(document.documentID)
}
completion(self.WholeDocument)
}
}
I try to gather this array in my contentView
func Connection()
{
self.Database.GetAllData{(tab) in
if tab.count > 0 {
self.WholeDocumentContentView = tab
}
else {
print("Not found")
}
}
}
How to retrieve WholeDocument array with closure ?
Thank you for the help.
If I'm understanding the structure/question correctly, your goal is to access the WholeDocument array in another SwiftUI view. The way to do that could be:
class UserData : ObservableObject {
// Use Firebase library to configure APIs
private var db = Firestore.firestore()
#Published var WholeDocument : Array<String> = []
init() {
db.collection("Users").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
completion([""])
} else {
for document in querySnapshot!.documents {
// print("\(document.documentID) => \(document.data())")
self.WholeDocument.append(document.documentID)
}
}
}
}
}
struct MyContentView : View {
#ObservedObject var database: UserData
var body: some View {
// access database.WholeDocument
List(database.WholeDocument, id: \.self) {
// do something
}
}
That way, when you initialize MyContentView with an instance of UserData, you will be able to observe WholeDocument in that view.

Flutter/Firebase : How can i access the current user without using '.then(...)' function

I'm trying to avoid using the .then((u) { return u.uid }) function in all my code where I need to access the current user's UID, instead just by calling getCurrentUser().uid for a much faster access. However, it gives me an error The getter 'uid' was called on null. but it's not null because it does print in the console but only after showing that it's null and the error at the end for some reason. I'm not well knowledge in the Future/Async/Await logic so any help would be greatly appreciated!
class UsersAPI {
final DatabaseReference usersRef = FirebaseDatabase.instance.reference().child(Config.users);
Future<FirebaseUser> currentUser() async {
return await FirebaseAuth.instance.currentUser();
}
FirebaseUser getCurrentUser() {
FirebaseUser user;
this.currentUser().then((u) {
user = u;
print('USER 1 $user'); // Prints after 'USER 2'
});
print('USER 2 $user'); // Prints first
if (user != null) {
return user;
} else {
return null;
}
}
DatabaseReference getCurrentUserRef() {
return this.usersRef.child(this.getCurrentUser().uid); // GIVES THE 'uid' WAS CALLED ON NULL ERROR
}
observeCurrentUser(Function onSuccess(User u)) {
this.usersRef.child(this.getCurrentUser().uid).onValue.listen( (event) { // GIVES THE 'uid' WAS CALLED ON NULL ERROR
DataSnapshot snapshot = event.snapshot;
if (snapshot.value != null) {
User user = User().transform(snapshot.key, snapshot.value);
onSuccess(user);
}
});
}
observeUser(String userID, Function onSuccess(User u), Function onFailure(String e)) {
this.usersRef.child(userID).onValue.listen( (e) {
DataSnapshot snapshot = e.snapshot;
if (snapshot.value != null) {
User user = User().transform(snapshot.key, snapshot.value);
onSuccess(user);
} else {
onFailure("User Not Found...");
}
});
}
}
Example Usage - WORKS:
APIs().usersAPI.currentUser().then((u) {
APIs().usersAPI.observeUser(u.uid, (u) {
onSuccess(u);
}, (e) {
print(e);
});
});
DOESN'T WORK:
APIs().usersAPI.observeCurrentUser((u) {
onSuccess(u);
});
DatabaseReference getCurrentUserRef() async {
return this.usersRef.child((await this.getCurrentUser()).uid); =
}
than call
var ref = await getCurrentUserRef()
Little bit more pretty
DatabaseReference getCurrentUserRef() async {
var firebaseUser = await this.getCurrentUser();
return this.usersRef.child(firebaseUser.uid);
}
EDIT: to clarify some question on asynchronicity
How would you call now this function to get the reference?
Lets say you want to update the data on the user, you can do
Firestore.instance.runTransaction((transaction) async {
var reference = await getCurrentUserRef();
await transaction.set(reference, someData);
});
Or you would like to read the data from that reference
readAndProcessData() async {
var reference = await getCurrentUserRef();
DocumentSnapshot user = await reference.get();
print(user.data.toString);
}

Resources