Create a Flutter plugin in swift to export realm data - swift

We have a native iOS app that has a Realm database. Now we have developed a new Flutter app that will substitute the native iOS app. When an user upgrade native iOS app to the new Flutter app, we want to migrate the existing Realm database content into the new Sqflite database.
We have created a Flutter plugin with swift to export Realm data to json. At the moment, we were able to add RealmSwift dependency to the plugin, but when we run the below code, it throws Cannot find 'DeviceModel' in scope.
Any idea to get all rows from DeviceModel table? To acomplish that is necesary to add some Realm schema manually?
import Flutter
import UIKit
import RealmSwift
enum PluginError: Error {
case notImplemented
}
public class SwiftRealmToJsonPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "realm_to_json", binaryMessenger: registrar.messenger())
let instance = SwiftRealmToJsonPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: #escaping FlutterResult) {
switch call.method {
case "getPlatformVersion":
result("iOS " + UIDevice.current.systemVersion)
case "realmInfoExist":
result(check_data())
default:
result(FlutterMethodNotImplemented)
}
}
// check if Realm exists
func check_data() -> Bool {
let realm = try! Realm()
let devices = realm.objects(DeviceModel.self)
}
}

The solution was:
Change plugin language from Swift to Objective-C and copy and paste all Realm models.
Implement in each model class a method to generate a dictionary with it data.
Export dictionary to json.
Right now we are able to export Realm info into a json format. Next step is implement the migration logic to adapt a table-less structure to sql.
Thanks to #Jay to point me out on the good direction to get a solution.

Related

NSXPCInterface setClasses causes "Lazily named class 0x600000xxxxxx wasn’t named by lazy name handler" on Xcode 14+ Swift #objc enum

After updating to Xcode 14 I am getting the SIGABRT crash "Lazily named class 0x600000dc6520 wasn’t named by lazy name handler". In the latest version of Xcode 13 it compiles and runs without any flaw. The only thing different from the very basic is that I use the MyAppSharedObjects to get across more complex objects between the XPC Service and the main app. Apart from that it doesn't look like anything beyond tutorial level NSXPCInterface code to me:
import MyAppScriptSandbox
import MyAppSharedObjects
import Foundation
class ScriptExecutor {
init?() {
let incomingClasses = NSSet(array: [
NSArray.self,
NSString.self,
NSValue.self,
NSNumber.self,
NSData.self,
NSDate.self,
NSNull.self,
NSURL.self,
NSUUID.self,
NSError.self,
NSDictionary.self,
ScriptSandboxReply.self,
AppleScriptError.self,
AppleScriptErrorType.self
]) as Set
let remoteInterface = NSXPCInterface(with: MyAppScriptSandboxProtocol.self)
remoteInterface.setClasses( // **** CRASH HAPPENS HERE ****
incomingClasses,
for: #selector(MyAppScriptSandboxProtocol.execute(script:withReply:)),
argumentIndex: 0,
ofReply: true
)
import Foundation
import MyAppSharedObjects
#objc
public protocol MyAppScriptSandboxProtocol {
func execute(
script: String,
withReply reply: #escaping (ScriptSandboxReply) -> Void
)
func terminate()
}
#objc(ScriptSandboxReply)
public class ScriptSandboxReply: NSObject, NSSecureCoding {
public static let supportsSecureCoding = true
public func encode(with coder: NSCoder) {
// Removed company specific code
}
required public init?(coder: NSCoder) {
// Removed company specific code
}
}
This data type was the issue:
#objc(AppleScriptErrorType)
public enum AppleScriptErrorType: Int {
case error
case noResult
case errorNorResult // This really shouldn't happen IRL
static let key = "AppleScriptErrorType"
}
After a bit of dabbing I found the issue. I started to comment out custom data types in the incomingClasses list and while the application crashed upon the first received message it stopped crashing on init. By uncommenting the custom data types one by one I finally found the culprit:
#objc(AppleScriptErrorType)
public enum AppleScriptErrorType: Int {
case error
case noResult
case errorNorResult // This really shouldn't happen IRL
static let key = "AppleScriptErrorType"
}
I have no idea why this perfectly sane enum type could not be transferred anymore after Xcode 14 dropped while it worked in Xcode 13.4.1, I think it might be an Apple bug. However I could get on with my life using the rawValue (so just the int value really) to send the value from one side and to reconstruct the AppleScriptErrorType enum on the receiving side.

NSKeyedUnarchiver decodeObjectForKey: cannot decode object of class for key (NS.objects)

I looked through whole SO but still no answer. My app reports this problem:
Fatal Exception: NSInvalidUnarchiveOperationException
*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (App_Title.Products) for key (NS.objects); the
class may be defined in source code or a library that is not linked
I did already do NSKeyedUnarchiver.setClass just before #unarchiveObject:
func loadProducts() -> [Products]? {
NSKeyedUnarchiver.setClass(Products.self, forClassName: "Products")
let unarchivedData = NSKeyedUnarchiver.unarchiveObject(withFile: Products.ArchiveURL.path)
My Products class begins with #Objc:
import Foundation
#objc(Products)
class Products: NSObject, Codable, NSCoding { ... }
Adding the two lines above which seemed to help people didn't bring me any luck. Before them and after them it's the same behaviour. I personally could never reproduce this issue myself.
During development I keep very closely to the app guide on peristance and reviewed it multiple times.
Just before NSKeyedArchiver I check for file existance:
let filePath = Products.ArchiveURL.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
Here some additional informations in screenshots.
The only place where I could find a real exception description was Firebase Crashalytics:
A screenshot from the Xcode Organizer under the Crash tab:
Which leads to this line in the code:
NSKeyedUnarchiver.setClass(Products.self, forClassName: "Products")
let unarchivedData = NSKeyedUnarchiver.unarchiveObject(withFile: Products.ArchiveURL.path)
Products class with #Objc annotation.
It seems that you are mixing up Codable and NSCoding. Don't try to use both simultaneously. NSKeyedUnarchiver belongs to NSCoding.
Your class contains property list compliant properties. Drop NSCoding and use only Codable. (By the way it's recommended to name the class in singular form Product).
Delete everything which is related to NSCoding including the protocol conformance and with Codable it's not necessary that the class must inherit from NSObject (the object can be even a struct).
The loadProducts function can be reduced to
func loadProducts() throws -> [Product] {
let data = try Data(contentsOf: Product.ArchiveURL)
return try PropertyListDecoder().decode([Product].self, from: data)
}
It's good practice to hand over thrown errors to the caller
And delete the CodingKeys, you don't need them if the keys match the property names.

Swift MVVM testing strategy and code coverage questions

I've run into an issue when generating code coverage with Xcode for view models in an MVVM environment.
Our basic setup is the view controller makes requests to the view model, which in turn calls methods on a data manager that talks to web services.
I came up with what I thought was a reasonably elegant way to test the view models by creating a fake data manager that subclasses the actual data manager and overrides the function called by the VM.
The problem is that for this to work, the VM must be part of the app target and the test target. An apparent side effect of this is that code coverage is not generated for items belonging to two or more targets, even though the unit tests pass. Code coverage is enabled in the project.
Here is a excerpted view model:
import Foundation
class BoosViewModel: BaseViewModel {
convenience override init() {
self.init(dataManager: BoosDataManager(), andModel: nil)
}
func getUnlinkedBoos(_ cardType: CardType) {
(dataManager as! BoosDataManager).getUnlinkedBoos(cardType) { result, error in
...stuff happens here...
}
}
}
... and the data manager
class BoosDataManager: DataManager {
static let SharedInstance: BoosDataManager = {
var manager = BoosDataManager()
return manager
}()
func getUnlinkedBoos(_ cardType: CardType = .loyalty, completion: #escaping ((_ result: BoosModel?, _ error: NSError?) -> Void)) {
...stuff happens here...
}
}
...and the test
class BoosViewModelTests: XCTestCase {
func testGetUnlinkedBoosHappyPath() {
class FauxDataManager: BoosDataManager {
override func getUnlinkedBoos(_ cardType: CardType = .loyalty, completion: #escaping ((_ result: BoosModel?, _ error: NSError?) -> Void)) {
...stuff happens here...
}
}
let viewModel = BoosViewModel()
let dataManager = FauxDataManager()
viewModel.dataManager = dataManager
viewModel.getUnlinkedBoos(.loyalty)
XCTAssertTrue(testObserver.updated)
XCTAssertEqual(testObserver.newViewModel.getBoos().count, 1)
}
}
As I noted earlier the unit tests in this scenario complete successfully, but unit coverage does not get generated.
I have older tests where I actually created an external fake data manager class that was used by the test, the class under test is not part of the test target, and coverage works fine.
The drawback to that is that I have to create multiple data managers to handle specific cases for its returns. If I can't encapsulate the classes, I would need to create a bunch of swift data managers, one for each scenario.
That's why I came up with the internal class.
Now, the problem comes in if I remove the view model under test from the testing target. After doing this, I add #testable import BoosApp to the unit test so that the view model under test can be resolved. When I do this, I get the following error:
Could not cast value of type 'BoosTests.BoosViewModelTests.(testGetUnlinkedBoosHappyPath () -> ()).(FauxDataManager #1)' (0x11f673d18) to 'Boos.BoosDataManager' (0x10444b128).
Aug 30 20:43:01 Pay[19025] : Could not cast value of type 'BoosTests.BoosViewModelTests.(testGetUnlinkedBoosHappyPath () -> ()).(FauxDataManager #1)' (0x11f673d18) to 'Boos.BoosDataManager' (0x10444b128).
I'm not sure what I'm missing. Is there a way to make this scenario work, or am I stuck creating multiple data managers outside of the test code?
Ultimately, I figured out the main issue was that the view model and data manager had somehow gotten added to the test target. After removing them from the test target I was able to make a couple of minor changes and everything is running fine. FYI.

How to properly convert a 3rd party library delegate into a RxSwift Observable

I have a case where I am using a 3rd party library and I would like to make it into an Observable. Appropriately, the library is designed around delegates as one would expect so I am wrapping it. The library performs an async operation and calls it's delegate with the results when it completes.
I definitely want to take advantage of the cold nature of the observable and only start the operation when someone subscribes. I have a solution that works, I just don't know if it's deeply flawed and I am missing some important understanding of RxSwift or perhaps there is a simpler way to achieve the same goal.
public final class RxLibBridge: LibDelegate{
let lib = Lib()
let _source = PublishSubject<[LibResult]>()
public init(){
lib.delegate = self
}
public func asObservable() -> Observable<[LibResult]>{
// create a cold observable to start
// the Lib's async operation on subscribe.
return Observable<Void>.create{
observer in
self.lib.startOperation()
// emit and complete
observer.onNext(())
observer.onCompleted()
return Disposables.create()
}
// convert the `Void` observable into an observable from the
// PublishSubject
.flatMapLatest{self._source}
}
// the lib's completion delegate method
public func lib(_ lib: Lib, didFinishWithResult results: [LibResult]) {
// grab the PublishSubject, emit the result and complete
let observer = _source.asObserver()
observer.onNext(results)
observer.onCompleted()
}
}
So my question is: Is this an appropriate pattern in Rx? Again, it works:
RxLibBridge()
.asObservable()
.subscribe(...)
Just because it works though doesn't mean I have not fundamentally misunderstood the proper way to work with this situation.
I know there is a way in RxSwift to handle something like this:
https://medium.com/#maxofeden/rxswift-migrate-delegates-to-beautiful-observables-3e606a863048#.rksg2ckpj
https://samritchie.net/2016/05/12/rxswift-delegateproxy-with-required-methods/
I tried this approach but it looks like the API changed since 2015. Namely, in the example links above proxyForObject cannot be found when adding the rx_delegate method in the extension.
Additionally, this approach appears to favor pure Objective-C [UIKit/AppKit] APIs. In my attempt to follow the linked example, I was editing the source of the 3rd party lib to make the delegate method optional and exposing it to #objc. The lib's delegate is required and I would rather not have to fork the lib to make the modifications.
This SO answer provided the updated API for the 2 links above:
Can not use proxyForObject function in DelegateProxyType (rxSwift)
So after digging some more, it looks like this will do the trick with a required delegate method, updated for RxSwift 3.3.1. This is using their DelegateProxy system.
import RxSwift
import RxCocoa
import Lib
public final class RxLibDelegate: DelegateProxy, LibDelegate, DelegateProxyType{
let _subject = PublishSubject<[LibResult]>()
public static func currentDelegateFor(_ object: AnyObject) -> AnyObject?{
let target = object as! Lib
return target.delegate
}
public static func setCurrentDelegate(_ delegate: AnyObject?, toObject object: AnyObject) {
let target = object as! Lib
target.delegate = delegate as? LibDelegate
}
public func lib(_ lib: Lib, didFinishWithResult results: [LibResult]) {
_subject.onNext(results)
_subject.onCompleted()
}
}
extension Lib{
public var rx_delegate: DelegateProxy{
// `proxyForDelegate` moved as compared to examples at:
// https://samritchie.net/2016/05/12/rxswift-delegateproxy-with-required-methods/
// https://medium.com/#maxofeden/rxswift-migrate-delegates-to-beautiful-observables-3e606a863048#.rksg2ckpj
return RxLibDelegate.proxyForObject(self)
}
public var rx_libResults: Observable<[LibResult]> {
// `proxyForDelegate` moved as compared to examples at:
// https://samritchie.net/2016/05/12/rxswift-delegateproxy-with-required-methods/
// https://medium.com/#maxofeden/rxswift-migrate-delegates-to-beautiful-observables-3e606a863048#.rksg2ckpj
let proxy = RxLibDelegate.proxyForObject(self)
return proxy._subject
}
}
That's about 28 LOC. My original "wrapper" (see updated version below) but I don't know if it's the best is 21 LOC; 6 of 1 half dozen of the other?
In my particular case I only have 1 delegate method to worry about. If you were working with some functionality that had multiple delegates I think the DelegateProxy + extension methods would be a lot more practical and the better choice in that case.
Regarding my original trial wrapping thing using that Void observable, it appears it's totally acceptable to alter the stream with flatMapLatest as evidenced here re: Sending continual events while a button is pressed:
https://stackoverflow.com/a/39123102/1060314
import RxSwift
import RxCocoa
let button = submitButton.rx_controlEvent([.TouchDown])
button
.flatMapLatest { _ in
Observable<Int64>.interval(0.1, scheduler: MainScheduler.instance)
.takeUntil(self.submitButton.rx_controlEvent([.TouchUpInside]))
}
.subscribeNext{ x in print("BOOM \(x)") }
.addDisposableTo(disposeBag)
//prints BOOM 0 BOOM 1 BOOM 2 BOOM 3 BOOM 4 BOOM 5 for every 0.1 seconds
Note that a new Observable is returned from flatMapLatest. The author cites the RxSwift slack channel, so I assume it is at least acceptable to do.
Here's an updated version of my wrapper version that I think might be a bit cleaner:
import RxSwift
public final class RxLibBridge: LibDelegate{
let lib = Lib()
let _source = PublishSubject<[LibResult]>()
public init(){
lib.delegate = self
}
public func asObservable() -> Observable<[LibResult]>{
// create a cold observable to start
// the Lib's async operation on subscribe.
return Observable.just(())
.do(onNext: {
self.lib.startOperation()
})
.flatMapLatest{self._source}
}
// the lib's completion delegate method
public func lib(_ lib: Lib, didFinishWithResult results: [LibResult]) {
// grab the PublishSubject, emit the result and complete
_source.onNext(results)
_source.onCompleted()
}
}

Using the Parse.com Object Column

I cannot find documentation on the Parse.com table column Object data type. I assume it is an object in the context of software development. However, what is the syntax to use to enter an object into the object column? I would be interested to know both the programmatic steps to take (not too concerned about which language, more concerned about actions to take to save to the object column), but I would be even more interested to know how to enter an object into the table from the Parse.com website. Can we do this from the Data section of the "Core" tab, in the Parse.com Dev webpage for the app?
I did this little test in Swift to try and save an object with a property of type object (myCar) to the table from code (I have a Parse.com table with class name Test, which has an object column called myCar). It is causing an error (I'm new to iOS so cannot find out much about the error):
Car class
class Car {
var doors = 4
func addDoor() {
doors++
}
}
client code:
override func viewDidLoad() {
var testMan = PFObject(className:"Test")
var car = Car()
testMan["myCar"] = car //////////////// error here
testMan.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
// The object has been saved.
println("Success")
} else {
// There was a problem, check error.description
println("Failure")
}
}
}
well, u're mixing swift objects with parse objects .. if you want a Car object linked to your Test class you must create it in parse database (+ add class -> car -> and then create properties in it). When you have created parse class Car u can use it in swift code as let car = PFObjecT(className: "Car") and later on assign that class to test object with testMan["myCar"] = car