mapping Swift Set of struct objects to kotlin Set of data classes with Kotlin Multiplatform Mobile - swift

For example, I've got this simple business code in kotlin:
class ProjectItemFacade(
val projects: Set<ProjectEntity>
) {
fun sortedProjects(): List<ProjectEntity> {
return projects.toList().sortedBy { it.name }
}
}
data class ProjectEntity(
val id: String,
val name: String,
val description: String
)
I try to call it from Swift like that:
struct ProjectItem: Identifiable, Codable, Hashable {
var id = UUID().uuidString
let name: String
let description: String
}
let projects: Set = [
ProjectItem(name: "Zero Project", description: "test1"),
ProjectItem(name: "First Project", description: "test2"),
ProjectItem(name: "Third Project", description: "test3"),
]
let sortedProjects = ProjectItemFacade(projects: projects).sortedProjects()
but it gives me an error:
Cannot convert value of type 'Set<ProjectItem>' to expected argument type 'Set<ProjectEntity>'
Cannot use instance member 'projects' within property initializer; property initializers run before 'self' is available
It seems like there is a problem with mapping Swift struct to kotlin data class. How to solve it?
EDIT
After renaming the kotlin class to the same as in swift it returns first error like that:
Cannot convert value of type 'Swift.Set<iosApp.ProjectItem>' to expected argument type 'Swift.Set<shared.ProjectItem>'

As #Sweeper said in comment, I should use kotlin class in my swift code. I decided to map my array like that:
import Foundation
import shared
struct ProjectItem: Identifiable, Codable, Hashable {
var id = UUID().uuidString
let name: String
let description: String
func toEntity() -> ProjectEntity {
ProjectEntity(id: id, name: name, description: description)
}
static func fromEntity(_ entity: ProjectEntity) -> ProjectItem {
ProjectItem(id: entity.id, name: entity.name, description: entity.description_)
}
}
Note that I use entity.description_ because it seems like description property call the data class toString() method.
let projects: Set = Set(
[
ProjectItem(name: "Zero Project", description: "test1"),
ProjectItem(name: "First Project", description: "test2"),
ProjectItem(name: "Third Project", description: "test3"),
]
.map { $0.toEntity() }
)
var sortedProjects: Array<ProjectEntity> {
get {
return ProjectItemFacade(projects: projects)
.sortedProjects()
}
}
Computed get solved the second error.

Related

RealmSwift LinkingObjects and Decodable

I have a Realm model class that I need to be Decodable so I can serialize it from JSON and save it to database. Every PortfolioItem is associated with one Product and at some point I need to get to PortfolioItem from Product via inverse relationship. That's why I have LinkingObjects property. The problem is when I try to conform to Decodable protocol. The compiler is giving me an error Cannot automatically synthesize 'Decodable' because 'LinkingObjects<PortfolioItem>' does not conform to 'Decodable' . How to deal with this? I found very little about LinkingObjects and Decodable online and I have no idea how to approach this.
class PortfolioItem: Object {
#objc dynamic var id: String = ""
#objc dynamic var productId: String = ""
#objc dynamic public var product: Product?
convenience init(id: String, productId: String) {
self.init()
self.id = id
}
}
final class Product: Object, Decodable {
#objc dynamic var id: String = ""
#objc dynamic var name: String = ""
private let portfolioItems = LinkingObjects(fromType: PortfolioItem.self, property: "product")
public var portfolioItem: PortfolioItem? {
return portfolioItems.first
}
convenience init(id: String, name: String) {
self.init()
self.id = id
}
}
Big thanks to Chris Shaw for helping me figure this out. I wrote a more in-depth article how to setup Decodable and LinkingObjects, look HERE.
Well unless I'm missing something then the LinkingObjects properties does not need to be included in the decoding.
My assumption here is that you're receiving JSON from some online source, where the JSON for a Product consists of { id: "", name: "" }. As long as you're creating the PortfolioItem correctly with associated Product, then the resulting LinkingObjects property is the result of a dynamic query within Realm (and will thus work without any JSON source).
I'm not in a position to test compile an answer today, but you should be able to use CodingKeys to simply exclude that property, i.e. by adding this to Product:-
private enum CodingKeys: String, CodingKey {
case id
case name
}
Also, unrelated, but note that your convenience init function are not initialising all properties that you're passing in.

how to create an array of a struct having a property belonging to another struct

i have two structs "Meal" and "Food"
i want to create an array of arrays
so these are my structs
struct Meal
{
var name : String;
var food : [Food];
}
struct Food
{
var name :String;
var description : String;
}
this is the code that im writing to creating the array :
var meals :[Meal] = [
Meal(name:"breakfast",food : [(name:"pancakes",description:"bk1"),(name:"waffles",description:"bk2")]),
Meal(name:"lunch",food : [(name:"pasta",description:"lunch1"),(name:"pizza",description:"lunch2")]),
Meal(name:"dinner",food : [(name:"rice",description:"din1"),(name:"noodles",description:"din2")]),
];
but it gives an error : "Cannot convert value of type '(name: String, description: String)' to expected element type 'Food' " .
how do i fix this?
Think about the syntax you are using to create a Food instance. Think about how you would create just one normally.
let someFood = Food(name: "pancakes", description: "bk1")
Use the same syntax in the array.
Meal(name: "breakfast", food: [Food(name: "pancakes", description: "bk1"), Food(name: "waffles", description: "bk2")]),

Cast between types using only protocols?

I have 2 structs that are only related by protocols. I'm trying to cast between the two types since they share a protocol, but I get a runtime error.
This is what I'm trying to do:
protocol A {
var id: String { get }
var name: String { get }
var isActive: Bool { get set }
}
struct Entity: A {
let id: String
var name: String
var isActive: Bool
func someFun() {
print("\(name), active: \(isActive)")
}
}
struct ViewModel: A {
let id: String
var name: String
var isActive: Bool
}
let vm = ViewModel(id: "abc", name: "John Doe", isActive: true)
let pt = vm as A
let et = pt as! Entity
print(et.someFun()) //crash
The error I get is:
Could not cast value of type '__lldb_expr_87.ViewModel' (0x111835638) to '__lldb_expr_87.Entity' (0x111835550).
This is a real bummer because if I have millions of records, I don't want to have to loop through each one to manually convert one by one. I was hoping to implicitly cast magically like this:
let entity = vm as A as Entity
let entities = vms as [A] as [Entity]
Any performance-conscious way to pass objects between boundaries without looping through each one?
As vadian stated in his comment, you can't typecast like this. To perform what you are trying to do by typecasting, you would need to do something like this:
let entity = Entity(id: vm.id, name: vm.name, isActive: vm.isActive)
let entities = vms.map {
return Entity(id: $0.id, name: $0.name, isActive: $0.isActive
}
The reason why you are crashing is because typecasting doesn't change the properties and methods that an object actually has, only the methods and properties they have access to. If you performed the following, you would lose access to your someFun() method:
let entityAsA = Entity(id: "id", name: "name", isActive: true) as A
However, the following will compile and run without crashing:
(entityAsA as! Entity).someFun()
So, when you attempt to cast from ViewModel to A to Entity, it doesn't change the way the value was created in memory (without access to a function called someFun() ) and you'll crash every time.

Protocol function implementation without actually conforming to a protocol

I am a beginner Swift learner and I have a question about protocols. I have followed a tutorial that teaches you about linked lists, which is as follows:
Node:
class LinkedListNode<Key> {
let key: Key
var next: LinkedListNode?
weak var previous: LinkedListNode?
init (key: Key) {
self.key = key
}
}
And the linked list:
class LinkedList<Element>: CustomStringConvertible {
typealias Node = LinkedListNode<Element>
private var head: Node?
// irrelevant code removed here
var description: String {
var output = "["
var node = head
while node != nil {
output += "\(node!.key)"
node = node!.next
if node != nil { output += ", " }
}
return output + "]"
}
}
The var description: String implementation simply lets you to print each elements in the linked list.
So far, I understand the structure of the linked list, my problem isn't about the linked list actually. What I don't understand is the protocol CustomStringConvertible. Why would it be wrong if I have only the var description: String implementation without conforming to the protocol? I mean, this protocol just simply say "Hey, you need to implement var description: String because you are conformed to me, but why can't we just implement var description: String without conforming to the protocol?
Is it because in the background, there is a function or some sort that takes in a type CustomStringConvertible and run it through some code and voila! text appears.
Why can't we just implement var description: String without conforming to the protocol?
Compare:
class Foo {
var description: String { return "my awesome description" }
}
let foo = Foo()
print("\(foo)") // return "stackoverflow.Foo" (myBundleName.Foo)
and
class Foo: CustomStringConvertible {
var description: String { return "my awesome description" }
}
let foo = Foo()
print("\(foo)") // return "my awesome description"
When you use CustomStringConvertible, you warrant that this class have the variable description, then, you can call it, without knowing the others details of implementation.
Another example:
(someObject as? CustomStringConvertible).description
I don't know the type of someObject, but, if it subscriber the CustomStringConvertible, then, I can call description.
You must conform to CustomStringConvertible if you want string interpolation to use your description property.
You use string interpolation in Swift like this:
"Here's my linked list: \(linkedList)"
The compiler basically turns that into this:
String(stringInterpolation:
String(stringInterpolationSegment: "Here's my linked list: "),
String(stringInterpolationSegment: linkedList),
String(stringInterpolationSegment: ""))
There's a generic version of String(stringInterpolationSegment:) defined like this:
public init<T>(stringInterpolationSegment expr: T) {
self = String(describing: expr)
}
String(describing: ) is defined like this:
public init<Subject>(describing instance: Subject) {
self.init()
_print_unlocked(instance, &self)
}
_print_unlocked is defined like this:
internal func _print_unlocked<T, TargetStream : TextOutputStream>(
_ value: T, _ target: inout TargetStream
) {
// Optional has no representation suitable for display; therefore,
// values of optional type should be printed as a debug
// string. Check for Optional first, before checking protocol
// conformance below, because an Optional value is convertible to a
// protocol if its wrapped type conforms to that protocol.
if _isOptional(type(of: value)) {
let debugPrintable = value as! CustomDebugStringConvertible
debugPrintable.debugDescription.write(to: &target)
return
}
if case let streamableObject as TextOutputStreamable = value {
streamableObject.write(to: &target)
return
}
if case let printableObject as CustomStringConvertible = value {
printableObject.description.write(to: &target)
return
}
if case let debugPrintableObject as CustomDebugStringConvertible = value {
debugPrintableObject.debugDescription.write(to: &target)
return
}
let mirror = Mirror(reflecting: value)
_adHocPrint_unlocked(value, mirror, &target, isDebugPrint: false)
}
Notice that _print_unlocked only calls the object's description method if the object conforms to CustomStringConvertible.
If your object doesn't conform to CustomStringConvertible or one of the other protocols used in _print_unlocked, then _print_unlocked creates a Mirror for your object, which ends up just printing the object's type (e.g. MyProject.LinkedList) and nothing else.
CustomStringConvertible allows you to do a print(linkedListInstance) that will print to the console whatever is returned by the description setter.
You can find more information about this protocol here: https://developer.apple.com/reference/swift/customstringconvertible

Extra argument in call when try to call convenience init, Swift

I have simple class:
class WmAttendee{
var mEmail:String!
var mName:String!
var mType:Int!
var mStatus:String = "0"
var mRelationShip:String!
init( email:String, name:String, type:Int) {
self.mEmail = email
self.mName = name
self.mType = type
}
convenience init( email:String, name:String, type:Int, status:String, relationShip:String) {
self.init(email: email, name: name, type: type)
self.mStatus = status
self.mRelationShip = relationShip
}
}
When I try to test 2nd constructor with 5 parameters, I get: Extra argument 'status' in call
var att1 = WmAttendee(email: "myMail", name: "SomeName", type: 1); // OK
var att2 = WmAttendee(email: "mail2", name: "name2", type: 3, status: "2", relationShip: 3)
// ERROR Extra argument 'status' in call
Why? Do I miss something?
Thanks,
Based on your method signature:
convenience init( email:String, name:String, type:Int, status:String, relationShip:String)
relationshipStatus should be a String and not an Int:
var att2 = WmAttendee(email: "mail2", name: "name2", type: 3, status: "2", relationShip: "3")
Since you're not passing the correct type for relationshipStatus, the compiler can't match the method signature for your convenience init and falls back the the default init (the closest match it can find) which triggers the Extra argument error.
Your are passing a parameter of the wrong type to your function. 'RelationShip' must be of type String, but you are passing an Integer. Yes, the compiler error is misleading, but then again swift is still in beta.