First of all, i'm new to Swift, so probably I have made some stupid mistake, but I have tried everything and I can't figure it out why this is happening.
So let me start. List property inside realm object is not being stored. That is first problem and second problem is that object I'm calling after adding object to realm loses it property value.
Here is the picture, so you guys can understand better what I'm saying
And now this is output:
PRODUCT BEFORE UPDATE: 34
Added new object
PRODUCT AFTER UPDATE:
I will show how my DBManager class looks:
import Foundation
import RealmSwift
class DBManager {
private var database:Realm
static let sharedInstance = DBManager()
private init() {
database = try! Realm()
}
func getDataFromDB() -> Results<Product> {
let results: Results<Product> = database.objects(Product.self)
return results
}
func getSingleDataFromDB(primaryKey: String) -> Product {
let product: Product = database.object(ofType: Product.self, forPrimaryKey: primaryKey)!
return product
}
func addData(object: Product) {
try! database.write {
database.add(object)
print("Added new object")
}
}
func updateData(object: Product) {
try! database.write {
database.add(object, update: true)
}
}
func deleteAllFromDatabase() {
try! database.write {
database.deleteAll()
}
}
func deleteFromDb(object: Product) {
try! database.write {
database.delete(object)
}
}
}
So If someone is seeing something that I'm not seeing, please let me know because I have lost many hours finding solution for this problem.
Thank you in advance
Related
I'm trying to implement a user based realm db. So each time a new users registers, a Realm file is created with his UID (from Firebase), or if exists, accesses that Realm.
To do that i created a singletone for the Realm calls and setting the Realm Configuration.
My issue now is that, every time i switch the users, the realm path changes (i'm printing after users logged in), but the Results reading are the same, as the users didn't change, or the realm did not switch. To be more clear, the queries results are giving same data.
If i open the actual Realms, the data is different.
I'm seeing this behavior in my tableView, just by loading data.
What am i doing wrong?
RealmServices:
import Foundation
import RealmSwift
import Firebase
class RealmServices {
private init() {}
static let shared = RealmServices()
let userID = Auth.auth().currentUser!.uid
var realm = try! Realm(configuration: Realm.Configuration.init(
fileURL: Realm.Configuration().fileURL!.deletingLastPathComponent().appendingPathComponent("\(Auth.auth().currentUser!.uid).realm")))
func create<T:Object> (_ object: T) {
do {
try realm.write {
realm.add(object)
}
} catch {
post(error)
}
}
func update <T: Object>(_ object: T, with dictionary: [String : Any?]) {
do {
try realm.write {
for (key,value) in dictionary {
object.setValue(value, forKey: key)
}
}
} catch {
post (error)
}
}
func delete<T:Object> (_ object: T) {
do {
try realm.write{
realm.delete(object)
}
} catch {
post (error)
}
}
func post (_ error: Error) {
NotificationCenter.default.post(name: NSNotification.Name("RealmError"), object: error)
}
func observeRealmErrors (in vc: UIViewController, completion: #escaping (Error?)-> Void) {
NotificationCenter.default.addObserver(forName: NSNotification.Name("RealmError"), object: nil, queue: nil) { (Notification) in
completion(Notification.object as? Error)
}
}
func stopObservingError (in vc: UIViewController){
NotificationCenter.default.removeObserver(vc, name: NSNotification.Name("RealmError"), object: nil)
}
}
Here's what I think is going on (I could be totally off but let me give it a shot)
Your RealmServices is a singleton and initialized only once. It's not a subclass where each time one is created, it's initialized
So any var's are set up and populated on first run - the realm property is not going to re-initialize each time it's accessed. For example, take this singleton
final class MySingleton {
private init() {}
var someUuid = UUID().uuidString
static let sharedInstance = MySingleton()
}
as you can see, someUuId is a 'randomly' generated uuid. If you access that property twice, it presents the same value, there's nothing to force an update.
print(MySingleton.sharedInstance.someUuid)
print(MySingleton.sharedInstance.someUuid)
results in
EBE131DE-B574-4CE1-8D74-E680D80A577B
EBE131DE-B574-4CE1-8D74-E680D80A577B
So, the solution is to set the users uid property in your question to a different value each time you want it changed. Here's a quick example
final class RealmService {
private init() {}
var uid: String!
var usersRealm: Realm! {
let path = Realm.Configuration().fileURL!.deletingLastPathComponent().appendingPathComponent("\(uid!).realm")
let realm = try! Realm(configuration: Realm.Configuration.init(fileURL: path))
return realm
}
static let sharedInstance = RealmService()
}
and here's how it's called; in this example we write item0 to user0's realm and item1 to user1's realm:
RealmService.sharedInstance.uid = "uid_0"
let user0Realm = RealmService.sharedInstance.usersRealm!
let item0 = ItemClass(name: "item 0")
try! user0Realm.write {
user0Realm.add(item0)
}
RealmService.sharedInstance.uid = "uid_1"
let user1Realm = RealmService.sharedInstance.usersRealm!
let item1 = ItemClass(name: "item 1")
try! user1Realm.write {
user1Realm.add(item1)
}
the result is a realm being created for each user with item0 in user0's realm and item1 being in user1's realm
As I understand, it is best to only test public methods of a class.
Let's have a look at this example. I have a view model for the view controller.
protocol MyViewModelProtocol {
var items: [SomeItem] { get }
var onInsertItemsAtIndexPaths: (([IndexPath]) -> Void)? { get set }
func viewLoaded()
}
class MyViewModel: MyViewModelProtocol {
func viewLoaded() {
let items = createDetailsCellModels()
updateCellModels(with: items)
requestDetails()
}
}
I want to test class viewLoaded(). This class calls two other methods - updateItems() and requestDetails()
One of the methods sets up the items and the other one call API to retrieve data and update those items. Items array us updated two times and onInsertItemsAtIndexPaths are called two times - when setting up those items and when updating with new data.
I can test whether after calling viewLoaded() expected items are set up and that onInsertItemsAtIndexPaths is called.
However, the test method will become rather complex.
What is your view, should I test those two methods separately or just write this one huge test?
By testing only viewLoaded(), my idea is that the implementation can change and I only care that results are what I expect.
I think the same thing, only public functions should be tested, since public ones use private ones, and your view on MVVM is correct. You can improve it by adding a DataSource and a Mapper that allows you to improve testing.
However, yes, the test seems huge to me, the tests should test simple units and ensure that small parts of the code work well, with the example you show is difficult, you need to divide by layers (clean code).
In the example you load the data into the viewModel and make it difficult to mockup the data. But if you have a Domain layer you can pass the UseCase mock to the viewModel and control the result. If you run a test on your example, the result will also depend on what the endpoint returns. (404, 200, empty array, data with error ...). So it is important, for testing purposes, to have a good separation by layers. (Presentation, Domain and Data) to be able to test each one separately.
I give you an example of how I would test a view mode, sure there are better and cooler examples, but it's an approach.
Here you can see a viewModel
protocol BeersListViewModel: BeersListViewModelInput, BeersListViewModelOutput {}
protocol BeersListViewModelInput {
func viewDidLoad()
func updateView()
func image(url: String?, index: Int) -> Cancellable?
}
protocol BeersListViewModelOutput {
var items: Box<BeersListModel?> { get }
var loadingStatus: Box<LoadingStatus?> { get }
var error: Box<Error?> { get }
}
final class DefaultBeersListViewModel {
private let beersListUseCase: BeersListUseCase
private var beersLoadTask: Cancellable? { willSet { beersLoadTask?.cancel() }}
var items: Box<BeersListModel?> = Box(nil)
var loadingStatus: Box<LoadingStatus?> = Box(.stop)
var error: Box<Error?> = Box(nil)
#discardableResult
init(beersListUseCase: BeersListUseCase) {
self.beersListUseCase = beersListUseCase
}
func viewDidLoad() {
updateView()
}
}
// MARK: Update View
extension DefaultBeersListViewModel: BeersListViewModel {
func updateView() {
self.loadingStatus.value = .start
beersLoadTask = beersListUseCase.execute(completion: { (result) in
switch result {
case .success(let beers):
let beers = beers.map { DefaultBeerModel(beer: $0) }
self.items.value = DefaultBeersListModel(beers: beers)
case .failure(let error):
self.error.value = error
}
self.loadingStatus.value = .stop
})
}
}
// MARK: - Images
extension DefaultBeersListViewModel {
func image(url: String?, index: Int) -> Cancellable? {
guard let url = url else { return nil }
return beersListUseCase.image(with: url, completion: { (result) in
switch result {
case .success(let imageData):
self.items.value?.items?[index].image.value = imageData
case .failure(let error ):
print("image error: \(error)")
}
})
}
}
Here you can see the viewModel test using mocks for the data and view.
class BeerListViewModelTest: XCTestCase {
private enum ErrorMock: Error {
case error
}
class BeersListUseCaseMock: BeersListUseCase {
var error: Error?
var expt: XCTestExpectation?
func execute(completion: #escaping (Result<[BeerEntity], Error>) -> Void) -> Cancellable? {
let beersMock = BeersMock.makeBeerListEntityMock()
if let error = error {
completion(.failure(error))
} else {
completion(.success(beersMock))
}
expt?.fulfill()
return nil
}
func image(with imageUrl: String, completion: #escaping (Result<Data, Error>) -> Void) -> Cancellable? {
return nil
}
}
func testWhenAPIReturnAllData() {
let beersListUseCaseMock = BeersListUseCaseMock()
beersListUseCaseMock.expt = self.expectation(description: "All OK")
beersListUseCaseMock.error = nil
let viewModel = DefaultBeersListViewModel(beersListUseCase: beersListUseCaseMock)
viewModel.items.bind { (_) in}
viewModel.updateView()
waitForExpectations(timeout: 10, handler: nil)
XCTAssertNotNil(viewModel.items.value)
XCTAssertNil(viewModel.error.value)
XCTAssert(viewModel.loadingStatus.value == .stop)
}
func testWhenDataReturnsError() {
let beersListUseCaseMock = BeersListUseCaseMock()
beersListUseCaseMock.expt = self.expectation(description: "Error")
beersListUseCaseMock.error = ErrorMock.error
let viewModel = DefaultBeersListViewModel(beersListUseCase: beersListUseCaseMock)
viewModel.updateView()
waitForExpectations(timeout: 10, handler: nil)
XCTAssertNil(viewModel.items.value)
XCTAssertNotNil(viewModel.error.value)
XCTAssert(viewModel.loadingStatus.value == .stop)
}
}
in this way you can test the view, the business logic and the data separately, in addition to being a code that is very reusable.
Hope this helps you, I have it posted on github in case you need it.
https://github.com/cardona/MVVM
In my application I have a custom RealmDatabase class. It initializes the Realm database for me.
public class RealmDatabase: Database {
let realm: Realm
//
// I also provide a shared() singleton
//
public init(fileURL: URL) {
let config = Realm.Configuration(fileURL: fileURL)
Realm.Configuration.defaultConfiguration = config
realm = try! Realm()
}
public func observe<T>(_ block: #escaping ((T) -> Void), for object: T.Type) where T : Storable {
realm.objects(object).observe { (changes) in
print("Changes: ", changes)
}
}
}
Now, I also wrote a class called SyncEngine so that I can start syncing with CloudKit. The class looks like this:
public class SyncEngine: NSObject {
private let database: Database
public init(database: Database) {
self.database = database
super.init()
}
public func start() {
database.observe({ restaurant in
print("changes!")
}, for: Restaurant.self)
}
}
Now, in my AppDelegate I do the following:
let database = RealmDatabase.shared()
let syncEngine = SyncEngine(database: database)
syncEngine.start()
The problem, though, is that my observer is never fired and print("Changes: ", changes) is never printed to the console. I don't know what I'm doing wrong, though. Any ideas?
You're discarding the observation as you create it. To solve this, you need to retain the NotificationToken returned by observe.
var token: NotificationToken?
func start() {
token = database.observe { changes in ... }
}
I'm trying to find the right way to update a list in a Realm object. For example:
class Person: Object {
#objc dynamic var id: String = ""
let children = List<Child>()
}
//Adding an item to the list
func add(child: Child, _ completion: (DBError?) -> Void) {
do {
let ctx = try Realm()
if let person = ctx.objects(Person.self).last {
try ctx.write({
person.children.append(child)
completion(nil)
})
} catch {
completion(DBError.unableToAccessDatabase)
}
}
This seems to work for adding an element. So how do I update an individual element in the array as well as replace the whole array to ensure it persists?
I'm having trouble with Alecrim Core Data in Xcode 8 Beta. The DataContext and DataContextOptions seem to be missing from the Swift_3 branch. Grasping at straws, I just updated the files from the master branch to Swift3 syntax hoping the functionality hadn't changed too much. But when it tries to read data I get an error message "Cannot fetch without an NSManagedObjectContext in scope".
I've done as much triage as I can think of. Breakpoints set at the spot that creates an NSManagedObjectContext and I can see it. The place that creates the fetchRequest has been updated with the new NSFetchReqeust syntax (although I see no sign of a MOC there).
Here's my link into the Alecrim lib DataContext:
let dataContext = DataContext()
extension DataContext
{
public var collections: Table<CoreDataCollection> { return Table<CoreDataCollection>(context: self) }
public var expressions: Table<CoreDataExpression> { return Table<CoreDataExpression>(context: self) }
public var fileAssets: Table<CoreDataFileAsset> { return Table<CoreDataFileAsset>(context: self) }
public var purchases: Table<CoreDataPurchase> { return Table<CoreDataPurchase>(context: self) }
public var reeConfigs: Table<CoreDataReeConfig> { return Table<CoreDataReeConfig>(context: self) }
public var stickers: Table<CoreDataSticker> { return Table<CoreDataSticker>(context: self) }
}
And the part that attempts to get data:
for item in dataContext.reeConfigs {
let reeConfigVO = ReeConfigVO()
reeConfigVO.initFromCoreData(item)
items.append(reeConfigVO)
}
I'm not sure if this will be helpful but the part of Alecrim that's tossing the error:
// MARK: - GenericQueryable
extension TableProtocol {
public final func execute() -> [Self.Element] {
do {
return try self.toFetchRequest().execute() as [Self.Element]
}
catch let error {
AlecrimCoreDataError.handleError(error)
}
}
}
// MARK: - CoreDataQueryable
extension TableProtocol {
public final func toFetchRequest<ResultType: NSFetchRequestResult>() -> NSFetchRequest<ResultType> {
let fetchRequest = NSFetchRequest<ResultType>()
fetchRequest.entity = self.entityDescription
fetchRequest.fetchOffset = self.offset
fetchRequest.fetchLimit = self.limit
fetchRequest.fetchBatchSize = (self.limit > 0 && self.batchSize > self.limit ? 0 : self.batchSize)
fetchRequest.predicate = self.predicate
fetchRequest.sortDescriptors = self.sortDescriptors
return fetchRequest
}
}
Does anyone have experience with Alecrim in Swift 3 that can help figure out what's going wrong?
Thanks,
Mike
I found the answer I was looking for. I'm not sure this is what Alecrim wants to be done because, as I mentioned, the swift_3 branch doesn't even contain DataContext or DataContextOptions. However... if you have brought those files in from the main branch and updated them to swift 3 as I have and you have problems with the error "Cannot fetch without an MSManagedObjectContext in scope" here's how to fix it in the "TableProtocol.swift" file:
extension TableProtocol {
public final func execute() -> [Self.Element] {
do {
return try self.context.fetch(self.toFetchRequest()) as! [Self.Element]
//return try self.toFetchRequest().execute() as [Self.Element]
}
catch let error {
AlecrimCoreDataError.handleError(error)
}
}
}
self.context is the NSManagedObjectContext (or a derived type i.e. our DataContext) that is needed for this to work. The as! [Self.Element] is crucial because Swift uses it to infer the RestultType in the function that builds the NSFetchRequest
Hope this helps.