I set breakpoints all throughout this query to Firebase, and all that's happening is the breakpoint for the following line gets hit (which is the very first line), and then no others.
_CHAT_REF.observeEventType(.Value, withBlock: { snapshot in
Any idea why this would be happening? Even if there is no data, the breakpoints inside the query block should be still be getting hit, but they aren't. What I've done: I've uninstalled and reinstalled Firebase at least 10 times, using both CocoaPods and not CocoaPods, following directions to the T. I'm not getting any kind of compile error, and I'm running FIRApp.configure() in my app delegate.
Full Code (breakpoints on each line, none called only _CHAT_REF.observe line):
private var _CHAT_REF = FIRDatabase.database().reference().child("chats")
_CHAT_REF.observeEventType(.Value, withBlock: { snapshot in
self.individualMessages = []
if snapshot.hasChildren() {
// Found chats
for snap in snapshot.children {
let theChat = Chat(snapshot: snap as! FIRDataSnapshot)
// The current user belongs to this chat, so add it to individual messages.
if theChat.sender_id == GlobalEnv.currentUser.id || theChat.receiver_id == GlobalEnv.currentUser.id {
self.individualMessages.append(theChat)
}
}
} else {
// No Children
print("No children found.")
}
self.tvContacts.reloadData()
})
DB Structure:
DB Structure on Firebase
I ran into a similar problem. It turned out that I wasn't able to read/write the database from behind my organization's proxy server. I built to a device using open wifi, and it worked.
Try this and let me know if it makes a difference. It's very simplified but the variable assignments are handled differently.
assume you are in a ViewController class...
class ViewController: UIViewController {
var ref: FIRDatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
ref = FIRDatabase.database().reference()
}
func clickAction() { //just tied to an action in the UI
let chatsRef = ref.child("chats")
chatsRef.observeEventType(.Value, withBlock: { snapshot in
print("Hello, World")
})
}
}
Related
I am getting started with RemoteConfig on iOS with Swift and followed the tutorial to get started. I have developer mode enabled and tested the updating of config values via the Firebase console. However, the update values never get synced with the local config values.
Code:
override func viewDidLoad() {
super.viewDidLoad()
syncRemoteConfig()
}
fileprivate func syncRemoteConfig() {
let remoteConfig = RemoteConfig.remoteConfig()
#if DEBUG
let settings = RemoteConfigSettings()
settings.minimumFetchInterval = 0
remoteConfig.configSettings = settings
#endif
remoteConfig.fetchAndActivate { (status, error) in
let posts = remoteConfig.configValue(forKey: "posts").jsonValue as! [[String: AnyObject]]
print(posts) // <== Always print the previous data
if let error = error {
print(error.localizedDescription)
}
//status always prints status.successUsingPreFetchedData
}
}
Run pod update to at least Firebase 6.25.0.
It fixed a race condition bug in which the minimumFetchInterval might not have been applied before the fetch.
I'm trying to port a little RSS feed reader app from UIKit over to SwiftUI, this app uses Realm for persistence.
In order to make Realm bindable in SwiftUI, I added the following code to my project:
import Foundation
import SwiftUI
import RealmSwift
final class FeedData: ObservableObject {
#Published var feeds: [Feed] {
didSet {
cleanRealm()
}
}
private var feedsToken: NotificationToken?
private func activateFeedsToken() {
let realm = try! Realm()
let feeds = realm.objects(Feed.self)
feedsToken = feeds.observe { _ in
self.feeds = Array(feeds)
}
}
func cleanRealm() {
let realm = try! Realm()
let tempFeeds = realm.objects(Feed.self)
let diff = feeds.difference(from: tempFeeds)
for change in diff {
switch change {
case .remove(_, let element, _):
do {
try realm.write {
print("Removing \(element.name)")
if element.isInvalidated {
print("Error: element invalidated.")
} else {
realm.delete(element)
print("Removed \(element.name)")
}
}
} catch {
fatalError(error.localizedDescription)
}
default:
break
}
}
}
init() {
let realm = try! Realm()
feeds = Array(realm.objects(Feed.self))
activateFeedsToken()
}
deinit {
feedsToken?.invalidate()
}
}
So, we have a feeds array with a DidSet observer that invokes the cleanRealm() function when triggered, which then uses collection diffing to remove Feed objects which are no longer in the array but still stored in Realm - I'm aware this is super clunky, but I figured it would at least keep the array in sync with the Realm database.
In my SwiftUI view, I then use FeedData as EnvironmentObject and use a List showing all Feed objects using ForEach.
When a user deletes a feed from the List, it is then removed from the array like so:
.onDelete(perform: deleteItems)
Which then calls this function:
func deleteItems(at offsets: IndexSet) {
print("Removing feeds at requested offsets.")
feedData.feeds.remove(atOffsets: offsets)
print("Removed feeds at requested offsets.")
}
Problem: when I run my app and then delete an entry from the list, the following exception is thrown:
* Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'
* First throw call stack:
(0x1a4a97ab0 0x1a47b1028 0x10098f6a8 0x100996324 0x1009962e8 0x10000dfa4 0x1b215d830 0x1b215d3f0 0x1b215d170 0x1b215f470 0x1db363c4c 0x1db36a0c8 0x1db36a240 0x1db36a6c0 0x1b22d647c 0x1db3ca2c4 0x1db3c9f04 0x1b21a924c 0x1b21a943c 0x1b21a9b50 0x1b22d647c 0x1daefcd50 0x1db3f0ac4 0x1db3eb7b8 0x1db3ead20 0x1db14dca4 0x1db14c5c0 0x1db4d5d0c 0x1db1bdc1c 0x1db1b836c 0x1db1bef70 0x1cf72c9c0 0x1cf713e9c 0x1cf714164 0x1cf719130 0x1db07f9f0 0x1db084e10 0x1db3ac770 0x1db07f96c 0x1cf719284 0x1db081ca0 0x1db081a48 0x1db0816c8 0x1db081834 0x1db3ac770 0x1db0817fc 0x1db09f848 0x1daf00a10 0x1daf00970 0x1daf00a8c 0x1a4a12668 0x1a4a0d308 0x1a4a0d8b8 0x1a4a0d084 0x1aec56534 0x1a8b7b8b8 0x10004e520 0x1a488ce18)
libc++abi.dylib: terminating with uncaught exception of type NSException
My research so far suggests that when a Realm object is deleted, it is mutated before being removed entirely.
So, I think what may be going on here is that when the object is mutated prior to being removed from Realm, SwiftUI detects this change, redraws the view and then tries accessing the now invalidated Realm object resulting in the exception being raised.
Realm objects do have a isInvalidated property that I should probably check before adding it to the List, but AFAIK (and please do feel free to correct me on this) there is no way in a ForEach block to check for such a condition and "continue" to the next array item if desired.
Any help with this is greatly appreciated, I've been messing with this issue all day and just cannot find a good solution, it's probably obvious but I'm also still learning about all the wonderful stuff SwiftUI can do while working on this sample project.
Thanks!
Update:
Following Jay's advice, I managed to modify my FeedData class so that it exposes a Realm Results property for my app to work with, instead of copying to a separate array.
That also means I no longer need to do any collection diffing when deleting feeds, but since SwiftUI's .onDelete(perform:) modifier expects a function that accepts an IndexSet, my delete function is still a bit of a hack.
The updated FeedData class now looks as follows:
import Foundation
import SwiftUI
import RealmSwift
final class FeedData: ObservableObject {
#Published var feeds: Results<Feed>
private var feedsToken: NotificationToken?
private func activateFeedsToken() {
let realm = try! Realm()
let feeds = realm.objects(Feed.self)
feedsToken = feeds.observe { _ in
self.feeds = feeds
}
}
func deleteItems(at offsets: IndexSet) {
print("deleteItems called.")
let realm = try! Realm()
do {
try realm.write {
offsets.forEach { index in
print("Attempting to access index \(index).")
if index < feeds.count {
print("Index is valid.")
let item = feeds[index]
print("Removing \(item.name)")
realm.delete(item)
print("Removed item.")
}
}
}
} catch {
fatalError(error.localizedDescription)
}
}
init() {
let realm = try! Realm()
feeds = realm.objects(Feed.self)
activateFeedsToken()
}
deinit {
feedsToken?.invalidate()
}
}
While that technically works, my issue now is that my view's ForEach() method will be called as soon as an object is deleted from the Realm (tested by using breakpoints), this happens even before the Realm notification is dispatched.
This then results in an attempt to access the index of the deleted object, in which case the app will crash with an index out of bounds error from Realm.
That should probably be a separate question though, as my original issue is resolved.
#Jay, can you please mark your first comment as an answer to my question so that I can approve it?
Thanks for the great help!
I ran into this problem, too. Not sure what's going on, some complicated multiple call behavior caused by SwiftUI, I think. When I expanded my notificationToken observation block and switch on the changes to return if a deletion is called, it works fine. In your case:
feedsToken = feeds.observe { [weak self] (changes: RealmCollectionChange) in
switch changes {
case .initial:
print("Initial call")
case .update(_, let deletions, _, _):
print("Updates made!")
if !deletions.isEmpty { return }
self.feeds = feeds
case .error(let error):
print("Error observing changes")
}
}
I am new to Realm DataBase and I need a way to read data from realmCloud, but from two different app projects. The way I have tried to implement this is by using query-synced realm. At the moment I'm using a singe realm user to write the data in one app, and the same realm user to read data from another app. The problem is that making a query from the second app(the one used for reading) doesn't return any realm objects ( I have also noticed that user identifier is different from the first one, and also the user permissions are nil.
I have tried setting permissions directly from RealmStudio since documentation is not precise on how to set them from code
func openRealm() {
do {
realm = try Realm(configuration: SyncUser.current!.configuration())
let queryResults = realm.objects(*className*.self)
let syncSubscription = queryResults.subscribe()
let notificationToken = queryResults.observe() { [weak self] (changes) in
switch (changes) {
case .initial: print(queryResults)
case .error(let error): print(error)
default: print("default")
}
}
for token in queryResults {
print(token.tokenString)
}
syncSubscription.unsubscribe()
notificationToken.invalidate()
} catch {
print(error)
}
}
This function prints the data in one app project, but used in another app project with the same user logged in, and the same classFile referenced in the project, it does not. (note that SyncUser.current.identifier is different also
There are a couple of issues.
Some of these calls are asynchronous and the code in your question is going out of scope before the data is sync'd (retreived). The bottom line is code is faster than the internet and you need to design the flow of the app around async calls; don't try to work with the data until it's available.
For example
let notificationToken = queryResults.observe() { [weak self] (changes) in
//here is where results are fully populated
}
// this code may run before results are populated //
for token in queryResults {
print(token.tokenString)
}
Also, let notificationToken is a local var and goes out of scope before the results are populated as well.
These issues are super easy to fix. First is to keep the notification token alive while waiting for results to be populated and the second is to work with the results inside the closure, as that's when they are valid.
var notificationToken: NotificationToken? = nil //a class var
func openRealm() {
do {
let config = SyncUser.current?.configuration()
let realm = try Realm(configuration: config!)
let queryResults = realm.objects(Project.self)
let syncSubscription = queryResults.subscribe(named: "my-projects")
self.notificationToken = queryResults.observe() { changes in
switch changes {
case .initial:
print("notification: initial results are populated")
queryResults.forEach { print($0) }
case .update(_, let deletions, let insertions, let modifications):
print("notification: results, inserted, deleteed or modified")
insertions.forEach { print($0) } //or mods or dels
case .error(let error):
fatalError("\(error)")
}
}
} catch {
print(error)
}
}
deinit {
self.notificationToken?.invalidate()
}
The other advantage of keeping that token (and its corresponding code) alive is when there are further changes, your app will be notified. So if another project is added for example, the code in the 'changes' section will run and display that change.
I am using RealmSwift to create a PIN code screen for an app. I have a manager class that has a few functions, including checkForExistingPin() which is intended to be used to check whether a pin exists (as the name suggests).
When I create an instance of the manager class and call the checkForExistingPin() function, it always tells me that there are 4 (It prints: "Optional(4)"), even though I have not created a pin yet.
Can anyone explain why this might be doing this and how I might get the correct output from the code?
Here is the class:
import Foundation
import RealmSwift
class pinCode: Object {
#objc dynamic var pin = ""
}
protocol pinCodeManager {
func checkForExistingPin() -> Bool
func enterNewPin(newPin:String)
func checkPin(pin:String) -> Bool
}
class manager:pinCodeManager {
let realm = try! Realm()
func checkForExistingPin() -> Bool {
let existingCode = realm.objects(pinCode.self).first?.pin
print("\n\nNumber of existing PINs: ", existingCode?.count as Any, "\n\n") // Number of existing PINs: Optional(4)
if existingCode?.count == 0 {
return false
}
else {
return true
}
}
func enterNewPin(newPin:String) {
if checkForExistingPin() {
let oldCode = realm.objects(pinCode.self).first
try! realm.write {
oldCode!.pin = newPin
}
}
let newPinObject = pinCode()
newPinObject.pin = newPin
realm.add(newPinObject)
}
func checkPin(pin:String) -> Bool {
if checkForExistingPin() {
print ("Realm object first: ", realm.objects(pinCode.self).first?.pin as Any)
if pin == realm.objects(pinCode.self).first?.pin {
print ("Pin Correct")
return true
}
else {
print ("Pin Incorrect")
return false
}
}
print ("No existing pin")
return false
}
}
And here is the relevant code snippet of the ViewController:
class InitialViewController: UIViewController {
let myPin = pinCode()
let myManager = manager()
let realm = try! Realm()
#IBAction func NewUserButton(_ sender: Any) {
print("No existing PINs: ", self.myManager.checkForExistingPin())
}
The output is : Number of existing PINs: Optional(4)
You must have created a pinCode object (or multiple of them). "Optional(4) doesn't mean you have created 4 pins. You are counting String. It means that the object you retrieved has a 4 digit pin. If you haven't created any pinCode object, you should get nil. Or if you have created one without assigning a pin, you should get 0.
I recommend your looking at your realm file. You should be able to print out its location this way:
print(Realm.Configuration.defaultConfiguration.fileURL!)
You can then open the file with Realm Studio and verify what is in there.
You have a few things going on here:
Although this is not really in the scope of the question, here's a tip for the future. Your types' names should be capitalized (following CamelCase standard), as per Swift API Design Guidelines. Thus, your pinCodes and manager classes and pinCodeManager protocol should be called PinCode, Manager and PinCodeManager respectively.
Assuming you renamed your types and as other users pointed out, you're not counting instances of PinCode. You're counting the length of the pin member of PinCode class. Refactoring your checkForExistingPin() function:
func checkForExistingPin() -> Bool {
return realm.objects(pinCode.self).count > 0
}
In your enterNewPin(newPin:) function, in the case you already have a PinCode object stored, note that you are actually updating the old PinCode and adding a new one with the same pin. For instance, if you previously have a PinCode object stored with pin=1234. After calling enterNewPin(newPin: "5678") you will have two such objects stored with the pin=5678. You might want to refactor that as well:
func enterNewPin(newPin:String) {
if checkForExistingPin() {
let oldCode = realm.objects(pinCode.self).first
try! realm.write {
oldCode!.pin = newPin
}
} else {
let newPinObject = pinCode()
newPinObject.pin = newPin
try! realm.write {
realm.add(newPinObject)
}
}
}
Before trying to do any debugging in your app. I recommend you first uninstalling and then reinstalling the app wherever you running (simulator or actual device). If things keep behaving weird, that's probably something related with your configuration if you're overriding the default one (i.e. I noticed that you just used try! Realm() for retrieving a Realm, but you might have overridden Realm.Configuration.defaultConfiguration somewhere else).
Hope that helps!
I'm developing an app on Firebase. When I manually reload the database by calling configureDatabase() for example in my viewDidLoad everything works fine. However, when the system refreshes itself some of the children are missing in the data snapshot. Perhaps I have to configure something to get all children?
Code
func configureDatabase() {
ref = FIRDatabase.database().reference()
// Listen for new messages in the Firebase database
_refHandle = self.ref.child(username).observeEventType(.ChildAdded, withBlock: { (snapshot) -> Void in
print(snapshot)
self.messages.append(snapshot)
self.tbl.reloadData()
})
}
When configureDatabase() is called
Snap (Group1) {
members = "[\"\Ud83d\Udc9c\": \"user1\", \"\Ud83c\Udf40\": \"user2\"]";
myScore = 0;
owner = user2; }
When it refreshes by itself
Snap (Group1) {
myScore = 0; }
Found the problem. I was using the .ChildAdded trigger but populating the child with children sequentially instead of all at once, so the method was called only the for the first addition, not the others.