Inserting data into realm DB with progress? - swift

I have request data which was about 7MB after it has downloaded the json string,means the json string is about 7MB.After it has downloaded,I would like to save the data into realm model object(table) with progress like
(1/7390) to (7390/7390) -> (data which is inserted/total data to be inserted)
I am using Alamofire as HTTPClient at my app.So,how to insert data with progress into my realm object model after it has downloaded from server?Any help cause I am a beginner.
I wont show the data model exactly,so,any example is appreciated.Let say my json string is.
{
{
name : Smith,
age : 23,
address : New York
},
{
name : Adam,
age : 22,
address : Maimi
},
{
name : Johnny,
age : 33,
address : Las Vegas
},
... about 7392 records
}

Supposing you have a label for do this.
Ok.
Supposing you using MVVM pattern too.
Ok.
ViewController has label and "observe"(*) the ViewModel property 'progress'
ViewModel has property 'progress'
class ViewModel: NSObject {
dynamic var progress: Int = 0
func save(object object: Object) {
do {
let realm = try Realm()
try realm.write({ () -> Void in
// Here your operations on DB
self.progress += 1
})
} catch let error as NSError {
ERLog(what: error)
}
}
}
In this way viewController is notified when "progress" change and you can update UI.
Your VC should have a method like this, called by viewDidLoad for instance:
private func setupObservers() {
RACObserve(self.viewModel, keyPath: "progress").subscribeNext { (next: AnyObject!) -> Void in
if let newProgress = next as? Int {
// Here update label
}
}
}
Where RACObserve is a global function:
import Foundation
import ReactiveCocoa
func RACObserve(target: NSObject!, keyPath: String) -> RACSignal {
return target.rac_valuesForKeyPath(keyPath, observer: target)
}
(*) You can use ReactiveCocoa for instance.

Katsumi from Realm here. First, Realm has no way to know the total amount of data. So calculating progress and notifying it should be done in your code.
A total is a count of results array. Store count as a local variable. Then define progress variable to store the number of persisted. progress should be incremented every save an object to the Realm. Then notify the progress.
let count = results.count // Store count of results
if count > 0{
if let users = results.array {
let progress = 0 // Number of persisted
let realm = try! Realm()
try realm.write {
for user in users{
let userList=UserList()
[...]
realm.add(userList,update: true)
progress += 1 // After save, increment progress
notify(progress, total: count)
}
}
}
}
So there are several ways to notify the progress. Here we use NSNotificationCenter for example:
func notify(progress: Int, total: Int) {
NSNotificationCenter
.defaultCenter()
.postNotificationName("ProgressNotification", object: self, userInfo: ["progress": progress, "total": total])
}

Related

How can I make retrieving data from firestore cloud faster when refreshing the page [duplicate]

I am new in programming and in iOS development. I am trying to make an app using Firestore database from Firebase. I don't know if it is normal or not, but when I am trying to get a data from firestore database, it seems too long for me. I don't know if I make a mistake or not
here is my code to get all city data from firestore
reference :
import Foundation
import FirebaseFirestore
import Firebase
enum FirestoreCollectionReference {
case users
case events
case cities
private var path : String {
switch self {
case .users : return "users"
case .events : return "events"
case .cities : return "cities"
}
}
func reference () -> CollectionReference {
return Firestore.firestore().collection(path)
}
}
I use getAllCitiesDataFromFirestore method in CityKM class to get the city data that stored in firestore
class CityKM {
var name : String
var coordinate : GeoPoint
init (name: String , coordinate: GeoPoint ) {
self.name = name
self.coordinate = coordinate
}
init (dictionary: [String:Any]) {
// this init will be used if we get data from firebase observation to construct an event object
name = dictionary["name"] as! String
coordinate = dictionary["coordinate"] as! GeoPoint
}
static func getAllCitiesDataFromFirestore (completion: #escaping ( [CityKM]? )->Void) {
// to retrieve all cities data from Firebase database by one read only, not using realtime fetching listener
let startTime = CFAbsoluteTimeGetCurrent() // to track time consumption of this method
FirestoreCollectionReference.cities.reference().getDocuments { (snapshot, error) in
if let error = error {
print("Failed to retrieve all cities data: \(error.localizedDescription)")
} else {
print("Sucessfully get all cities data from firestore")
guard let documentsSnapshot = snapshot, !documentsSnapshot.isEmpty else {
completion(nil)
return
}
let citiesDocuments = documentsSnapshot.documents
var cityArray = [CityKM]()
for document in citiesDocuments {
guard let cityName = document.data()["name"] as? String,
let cityCoordinate = document.data()["coordinate"] as? GeoPoint else {return}
let theCity = CityKM(name: cityName, coordinate: cityCoordinate)
cityArray.append(theCity)
}
completion(cityArray)
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime // to track time consumption of this method
print("Time needed to get all cities data from Firestore : \(timeElapsed) s.") // to track time consumption of this method
}
}
}
}
extension CityKM {
// MARK: - User Helper Methods
func toDictionary() -> [String:Any]{
return [
"name" : name,
"coordinate" : coordinate
]
}
}
from my debugging area, it is printed
"Time needed to get all cities data from Firestore : 1.8787678903 s."
is it possible to make it faster? Is 1.8s normal? am i make a mistake in my code that make the request data takes too long time ? I hope that I can make request time is below one second
I don't think the internet speed is the problem, since I can open video on youtube without buffering
That performance sounds a bit worse than what I see, but nothing excessive. Loading data from the cloud simply takes time. A quick approach to hide that latency is by making use of Firebase's built-in caching.
When you call getDocuments, the Firebase client needs to check on the server what the document's value is before it can call your code, which then shows the value to the user. As said: there is no way to speed up this reading in your code, so it'll always take at least 1.8s before the user sees a document.
If instead you listen for realtime updates from the database with addSnapshotListener, the Firebase client may be able to immediately call your code with values from its local cache, and then later re-invoke your code in case there has been an update to the data on the server.

How to Do Interface-driven Write with Realm when Datasource is Results Object

The Realm documentation for an interface-driven write indicates that you can add a record to a collection like this:
func insertItem() throws {
// Perform an interface-driven write on the main thread:
collection.realm!.beginWrite()
collection.insert(Item(), at: 0)
// And mirror it instantly in the UI
tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
// Making sure the change notification doesn't apply the change a second time
try collection.realm!.commitWrite(withoutNotifying: [token])
}
This seems to imply that the datasource for the table is an Array because there is not an insert method on a Results<Item> collection.
What data type is collection in this situation? It seems like it's an array, but my understanding is that you can't create a Realm notification on an array.
I've also read that it's not a good idea to copy all your Realm objects into an array (for performance reasons) since the results are accessed lazily. But it would seem that interface-driven writes are impossible unless you do. 🤔
Any suggestions on how to properly set this up?
The documentation is a bit vague but the specific answer to your question is that in this case, collection is a List type. See the documentation for Collections.
To drill down a bit, here's an example implementation.
Suppose we have a Person Object and each person has a List property of Dog Objects.
class PersonClass: Object {
#objc dynamic var person_name = ""
let dogs = List<DogClass>()
}
class DogClass: Object {
#objc dynamic var dog_name = ""
#objc dynamic var dog_age = 0
let owners = LinkingObjects(fromType: PersonClass.self, property: "dogs")
}
We want to know when a specific person gets a new dog, update our tableView with that dog info immediately and not receive a Realm notification for the event.
Here's the code to set up an observer for Jean-Luc's dogs.
//a class var for the notification and the List.
var dogListNotificationToken: NotificationToken? = nil
var dogList: List<DogClass>? = nil
func doObserveDogList() {
if let realm = gGetRealm() { //using a singleton Realm for this example
let personResults = realm.objects(PersonClass.self).filter("name == 'Jean-Luc'")
let firstPerson = personResults.first
self.dogList = firstPerson?.dogs
}
if self.dogList == nil {
print("there were no dogs for this person")
return
}
self.dogListNotificationToken = self.dogList!.observe { (changes: RealmCollectionChange) in
switch changes {
case .initial:
print("initial object load complete")
if let results = self.dogList {
for d in results {
print(d.dog_name, d.dog_age)
}
}
break
case .update(_, let deletions, let insertions, let modifications):
print(" handle item delete, insert or mod")
for index in deletions {
print(" deleted object at index: \(index)")
}
for index in insertions {
print(" inserted object at index: \(index)")
}
for index in modifications {
print(" modified object at index: \(index)")
}
break
case .error(let error):
// An error occurred
fatalError("\(error)")
break
}
}
}
and suppose Jean-Luc get gets a new dog so we need to insert that into realm but don't want a notification because we want to handle it 'immediately'.
func insertDog() {
let dogToAdd = DogClass()
dogToAdd.dog_name = "Number 1"
dogToAdd.dog_age = 5
self.dogList?.realm?.beginWrite()
self.dogList?.insert(dogToAdd, at: 0)
//insert into tableView/datasource/whatever
try! self.dogList?.realm!.commitWrite(withoutNotifying: [self.dogListNotificationToken!])
}
The above will result in the Dog 'Number 1' being added to Jean-Luc's dog list with no observe event being triggered upon the insert.

Working with Arrays and Firebase

Looking to submit an array to firebase as a list of objects with integers as key names. I know that firebase does not support Arrays directly so was wondering how to do so. I am creating a list of items users add a users cart. so I am approaching it as such:
func addItemtoCart(item: String, completed: #escaping (_ completed: Bool) -> ()) { Firebase_REference_Cart.child(userID).child("itemIDs").updateChildValues(["itemiD": itemID])
completed(true)
}
I understand that this will not work because every time and item is added to the cart it will replace the item in under the "ItemId". I was looking to have something like this
CartITems: {
0: "945495949856956",
1: "9459469486895695"
2: "J888568567857685"
}
If someone could please describe how to do this from A to Z in the most descriptive way possible it would help greatly. I am new to firebase and need a bit of guidance.
First of all create model like:
struct Model {
var id: String
init(id: String) {
self.id = id
}}
Then make an instance of this model in your class: var model = Model(id: "First Id")
Then to push it to Firebase use:
func addInstance(model: Model) -> Completable {
return Completable.create( subscribe: { completable in
let reference = Database.database()
.reference()
.child("Models")
.childByAutoId
var model = model
model.id = reference.key
reference.setValue(model.dictionary)
self.modelVariable.value.append(model)
completable(.completed)
}
else {
completable(.error(NSError(domain: "Sample", code: 403, userInfo: [NSLocalizedDescriptionKey: "Server Error"])))
}
return Disposables.create()
})

realm commit write error - Can't commit a non-existing write transaction

I am trying to add a record to a realm DB table.
I have a class Connection which represents a table I need in my DB and have created dynamic vars which are to represent the columns:
import Foundation
import RealmSwift
import Realm
open class ConnectionState: Object {
open dynamic var _id : String = NSUUID().uuidString
open dynamic var a : String = ""
open dynamic var b : String = ""
open dynamic var c : Int = 0
open override class func primaryKey() -> String? {
return "_id"
}
required public init() {
super.init()
}
required public init(realm: RLMRealm, schema: RLMObjectSchema) {
super.init(realm: realm, schema: schema)
}
required public init(value: Any, schema: RLMSchema) {
fatalError("init(value:schema:) has not been implemented")
}
}
Then in my code I am trying to write and commit the write transaction like so:
let ConnectionState = ConnectionState()
ConnectionState.a = "a"
ConnectionState.b = "b"
ConnectionState.c = 1
try! self.realm.write {
self.realm.add(ConnectionState)
}
try! self.realm.commitWrite()
When running this code, I am receiving the error:
Can't commit a non-existing write transaction
What am I missing? Do I need to have inits in my ConnectionState class?
Before adding in the commitWrite, I was trying to view the db with realm browser. I found my device in xCode and chose to download the container but it was empty. Then I thought I needed to add in commitWrite
In your example you called commitWrite without having called beginWrite. You cannot commit a write transaction because you did not start one. Either start a write transaction or delete the commitWrite line.
Start transaction and commit it
self.realm.beginWrite()
self.realm.add(ConnectionState)
try! self.realm.commitWrite()
Delete commitWrite
try! self.realm.write {
self.realm.add(ConnectionState)
}
The Realm docs have two examples of adding data to the database.
Use the realm.write method
// Use them like regular Swift objects
let myDog = Dog()
myDog.name = "Rex"
myDog.age = 1
print("name of dog: \(myDog.name)")
// Get the default Realm
let realm = try! Realm()
// Query Realm for all dogs less than 2 years old
let puppies = realm.objects(Dog.self).filter("age < 2")
puppies.count // => 0 because no dogs have been added to the Realm yet
// Persist your data easily
try! realm.write {
realm.add(myDog)
}
Use realm.beginWrite() and realm.commitWrite() to start the write transaction and commit data to the database
let realm = try! Realm()
// Break up the writing blocks into smaller portions
// by starting a new transaction
for idx1 in 0..<1000 {
realm.beginWrite()
// Add row via dictionary. Property order is ignored.
for idx2 in 0..<1000 {
realm.create(Person.self, value: [
"name": "\(idx1)",
"birthdate": Date(timeIntervalSince1970: TimeInterval(idx2))
])
}
// Commit the write transaction
// to make this data available to other threads
try! realm.commitWrite()
}
try! self.realm.write {
self.realm.add(ConnectionState)
}
This code is somewhat equivalent to (possibly with some additional error handling):
realm.beginWrite()
...
try! realm.commitWrite()
Which means you're trying to commit your writes twice.
Just change your code like this:
try! self.realm.write {
self.realm.add(ConnectionState)
}
// try! self.realm.commitWrite()

Realm 1.0 How can I use thread

I would like to use the realm in my project, but I have a very complex filter and sort. I have to order the list by name,but the name is in other class.
class CustomObject: Object
{
dynamic var objectId = 0
let objectLangs = List<ObjectLang>()
}
class ObjectLang: Object
{
dynamic var objectId = 0
dynamic var name = ""
}
When I have more than 130 rows, it is very slow in main thread and it blocks the UI. I tried do it in a background thread, but when I want to update the UI, it was crashed by Realm. So what is the perfect solution? How could I use it? Could you give me an example or tutorial? I have read the guide line.
If program is crashed when updating UI on background thread, you should update UI on main thread when realm task finished.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), {
let realm = try! Realm()
//do What you need
dispatch_async(dispatch_get_main_queue(), {
//updateUI()
})
})
You can't use Realm accessors across threads. You will need to retrieve the objects on the thread on which you want to use them. To make that happen, I'd recommend for each of your object classes which need to be passed between threads to designate a property as primary key. This property might be objectId in your case.
class CustomObject: Object {
dynamic var objectId = 0
let objectLangs = List<ObjectLang>()
override class func primaryKey() -> String {
return "objectId"
}
}
This primary key can then be used to identify your objects and pass them over to the main thread to retrieve them there again.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) {
let realm = try! Realm()
var objects = realm.objects(CustomObject)
// objects = objects.filter(…)
let sortedObjects: [CustomObject] = objects.sort { /* … */ }
let ids = sortedObjects.map { $0.objectId }
dispatch_async(dispatch_get_main_queue()) {
let realm = try! Realm()
let objects = ids.map {
realm.objectForPrimaryKey(CustomObject.self, key: $0)
}
updateUIWithObjects(objects)
}
}