I'm working in a fresh Kotlin Multiplatform mobile project, and I am having trouble implementing a Kotlin interface into a Swift class.
Here is my setup:
From kotlin common (shared) module:
interface LocalUserSource {
suspend fun saveUser(user: User): Boolean
suspend fun readUser(): User?
}
Implementing the protocol in Swift (I believe the protocol is generated by Kotlin/Native):
class DBUserSource : LocalUserSource {
func readUser(completionHandler: #escaping (common.User?, Error?) -> Void) {
// read user from core data
}
func saveUser(user: common.User, completionHandler: #escaping (KotlinBoolean?, Error?) -> Void) {
// save user with core data
}
}
The Xcode project is able to see the generated common framework, and I am able to jump to class / protocol definitions within the framework
But building the Xcode project continually results in this error:
Type 'DBUserSource' does not conform to protocol 'LocalUserSource'
When I use the "Fix" option in Xcode, it continually duplicates the method over and over and shows the same error. I've tried everything to clean both android studio (where I'm running the gradle build) and Xcode.
What's odd is, I've seen this work. I've saved and read users to core data, but today I cannot get the iOS side of things to work. Just wondering if anyone has experienced anything similar, and has any pointers.
Also here is the objective-c definition from the common framework:
__attribute__((swift_name("LocalUserSource")))
#protocol CommonLocalUserSource
#required
- (void)readUserWithCompletionHandler:(void (^)(CommonUser * _Nullable_result, NSError * _Nullable))completionHandler __attribute__((swift_name("readUser(completionHandler:)")));
- (void)saveUserUser:(CommonUser *)user completionHandler:(void (^)(CommonBoolean * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("saveUser(user:completionHandler:)")));
#end;
suspend fun readUser(): User? is a nullable in your Kotlin code, whereas you're using a non-nullable/non-optional type in the Swift equivalent function signature:
func readUser(completionHandler: #escaping (common.User, Error?) -> Void) {
// read user from core data
}
// The above should be
func readUser(completionHandler: #escaping (common.User?, Error?) -> Void) {
// read user from core data
}
So I finally figured it out. I had a generic Result class in my common module that looked like this:
sealed class Result<out T : Any>
class Success<out T : Any>(val data: T) : Result<T>()
class Error(private val exception: Throwable, val message: String? = exception.message) : Result<Nothing>()
inline fun <T : Any> Result<T>.onSuccess(action: (T) -> Unit): Result<T> {
if (this is Success) action(data)
return this
}
inline fun <T : Any> Result<T>.onError(action: (Error) -> Unit): Result<T> {
if (this is Error) action(this)
return this
}
Once I removed this, I no longer saw the implementation error in the Swift code and the project ran. Honestly, no idea why. I assume something with generics and Kotlin/Native. But, if anyone has any idea, I'd love to know!
Related
I have setup a Kotlin Multiplatform project and attached a SQLDelight database to it. Its all setup and running correctly as i have tested it on the android side using the following:
commonMain:
val backgroundColorFlow: Flow<Color> =
dbQuery.getColorWithId(BGColor.id)
.asFlow()
.mapToOneNotNull()
which triggers fine in the Android projects MainActivity.kt using:
database.backgroundColorFlow.onEach { setBackgroundColor(it.hex) }.launchIn(lifecycleScope)
but when trying to access the same call in the iOS projects app delegate i get the following options and im unsure how to use them or convert them into my BGColor object:
database.backgroundColorFlow.collect(collector: T##Kotlinx_coroutines_coreFlowCollector, completionHandler: (KotlinUnit?, Error?) -> Void)
can anyone help me with how to use this?
So this was resolved by creating a flow helper:
import io.ktor.utils.io.core.Closeable
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun <T> Flow<T>.asCommonFlow(): CommonFlow<T> = CommonFlow(this)
class CommonFlow<T>(private val origin: Flow<T>) : Flow<T> by origin {
fun watch(block: (T) -> Unit): Closeable {
val job = Job()
onEach {
block(it)
}.launchIn(CoroutineScope(Dispatchers.Main + job))
return object : Closeable {
override fun close() {
job.cancel()
}
}
}
}
My backgroundColorFlow var is update as follows to utilise this helper:
val backgroundColorFlow: CommonFlow<BGColor> =
dbQuery.getColorWithId(BGColor.id)
.asFlow()
.mapToOneNotNull()
.map { BGColor(it.name) }
.asCommonFlow()
Then my swift works as follows:
database.backgroundColorFlow.watch { color in
guard let colorHex = color?.hex else {
return
}
self.colorBehaviourSubject.onNext(colorHex)
}
and android like so:
database.backgroundColorFlow.watch { setBackgroundColor(it.hex) }
Hope this helps anyone that comes across this. I would like to convert the CommonFlow class into an extension of Flow but don't have the know-how atm so if any could that IMHO would be a much nicer solution
You can do it in swift, with the mentioned collect method
FlowCollector is a protocol which can be implemented to collect the data of the Flow object.
Generic example implementation could look like:
class Collector<T>: FlowCollector {
let callback:(T) -> Void
init(callback: #escaping (T) -> Void) {
self.callback = callback
}
func emit(value: Any?, completionHandler: #escaping (KotlinUnit?, Error?) -> Void) {
// do whatever you what with the emitted value
callback(value as! T)
// after you finished your work you need to call completionHandler to
// tell that you consumed the value and the next value can be consumed,
// otherwise you will not receive the next value
//
// i think first parameter can be always nil or KotlinUnit()
// second parameter is for an error which occurred while consuming the value
// passing an error object will throw a NSGenericException in kotlin code, which can be handled or your app will crash
completionHandler(KotlinUnit(), nil)
}
}
The second part is calling the Flow.collect function
database.backgroundColorFlow.collect(collector: Collector<YourValueType> { yourValue in
// do what ever you want
}) { (unit, error) in
// code which is executed if the Flow object completed
}
probably you also like to write some extension function to increase readability
I'm developing a new project from scratch and there are some concepts about this architecture that I would like to implement in the best way.
In many references on the Internet I can see some examples to deal with Clean Swift Architecture. In this architecture you can find that Interactor has a dependency of Worker classes
protocol InteractorLogic {
// Functions
}
class Interactor: InteractorLogic {
var presenter: PresenterLogic?
var worker: AWorker?
}
The doubt I have is about implementing this Worker because I saw in some examples that inside this Worker they put all the "use cases" related to the scene:
protocol SceneWorkerLogic {
func getPosts(success: ((Posts) -> Void), error: ((Error) -> Void)))
func getUser(success: ((User) -> Void), error: ((Error) -> Void)))
}
class SceneWorker {
func getPosts(success: ((Posts) -> Void), error: ((Error) -> Void))) {
}
func getUser(success: ((User) -> Void), error: ((Error) -> Void))) {
}
}
and in another examples, they divide functionality in different Workers like UserWorker and PostWorker where CRUD functions related with Users and Posts are implemented:
protocol PostWorkerLogic {
func getPosts(success: ((Posts) -> Void), error: ((Error) -> Void)))
}
class PostWorker {
func getPosts(success: ((Posts) -> Void), error: ((Error) -> Void))) {
}
}
I consider that the second approach is better in terms of Clean Code but I've realized that it could be even cleaner if we use a file for a single CRUD operation like getUser or getPosts.
How can I improve this?
There are a lot of ways to do! The way that was showed on the clean swift book isn't my favorite...
I prefer to use workers with dependency injections to use as a facade pattern to get the data! Doing this you will have the scene worker and specific workers to do the jobs.
Example: You have a scene that present a list of movies. This list could be fetched from network or coredata so you will have a worker that receive the kind of datasource that you will use on its init method and implements a protocol with the method getMovies and this will be the scene worker! Then you will have other worker to get this from network and another one for coredata, both of them must to implement that protocol with getMovies!
I think that will be better if you get a sample app that i built from scratch using this architecture to understand it better! I really think that this way is much better than the way that he uses on his clean swift sample app (clean store).
Here is my sample app using clean swift: https://github.com/chavitos/TheMovieDB
I'm doing some refactoring to move code to using Promises (using Hydra) instead of async callbacks. I originally had this method and it worked fine:
static func fetch<D: AnyDTO, E: AnyEntity>(
_ context : NSManagedObjectContext,
fetch request : NSFetchRequest<E>,
successHandler : #escaping ([D]) -> (),
errorHandler : #escaping ErrorHandler)
So I changed this to work with promises like this:
import Hydra
static func fetch<D: AnyDTO, E: AnyEntity>(
_ context : NSManagedObjectContext,
fetch request : NSFetchRequest<E>) -> Promise<[D]>
{
return Promise<[D]>(in: .background) { resolve, reject, _ in
...
}
}
with client code trying to call the function like this:
let request: NSFetchRequest<Location> = Location.fetchRequest()
return CacheUtils.fetch(context, fetch: request)
But the compiler is giving me this error:
Cannot convert value of type 'NSFetchRequest' to expected
argument type 'NSFetchRequest<_>'
and I'm unsure why. I've checked out similar questions, and noticed the issue of using a concrete type within the function (see this). I think Promise might fit this bill, but on the other hand, the Promise is generic, so I'm not confident that is the problem. Is it possible to do what I'm trying to achieve in Swift?
I have a several protocols set up in my framework to deal with resources. In one of the protocols, I have set up an extension to provide a default implementation for a decode function. It's simpler to show the code and what happens (see calls to fatalError). There's a lot more code in the actual implementation, but this illustrates the issue:
This is the "base" protocol:
public protocol Resourceful {
associatedtype AssociatedResource
typealias ResourceCompletionHandler = (AssociatedResource?, Error?) -> Void
func fetch(_ completion: #escaping ResourceCompletionHandler)
}
This is a generic, concrete implementaion of Resourceful:
open class WebResourceApiCall<Resource>: Resourceful {
public typealias AssociatedResource = Resource
public typealias FetchedResponse = (data: Data?, urlResponse: URLResponse?)
public init() {
}
public func fetch(_ completion: #escaping ResourceCompletionHandler) {
try! decode(fetched: (data: nil, urlResponse: nil))
}
public func decode(fetched: FetchedResponse) throws -> Resource {
fatalError("It ends up here, but I don't want it to!")
}
}
extension WebResourceApiCall where Resource: Decodable {
public func decode(fetched: FetchedResponse) throws -> Resource {
fatalError("This is where I want it to go...")
}
}
This is how I'm attempting to use it:
public struct Something: Decodable { }
var apiCall = WebResourceApiCall<Something>()
apiCall.fetch { _, _ in } // Implictly calls decode... but not the decode I expected it to! See fatalError() calls...
Instead of calling decode on the extension, like I hoped it would, the "default" decode method with no constraints is always called.
Why doesn't this work the way I expect it to?
Thanks in advance!
Swift is a statically dispatched language, thus the address of the decode() function to be called is computed at compile time, and because the call happens inside the base definition of the class, the compiler picks the original implementation.
Now, if you call the method from a place where the compiler has enough information to pick the implementation you need, it will work:
var apiCall = WebResourceApiCall<Something>()
try apiCall.decode(fetched: (nil, nil))
The above code will call the method from the specialized extension, as at this point the compiler is a better position to know that it has a more specialized implementation to call.
It should be possible to achieve the behaviour you need if you move the decode() method in the dynamic dispatch world - i.e. at the protocol level.
To mock objects in Swift for test, I generally follow a pattern of authoring a Protocol describing the behaviour of the object I'd like, and then using Cuckoo to generate mocks for it for test.
Usually, these protocols map directly onto existing types, and this works fine, until I need to make the existing type work with my new protocol types.
public typealias RequestCompletionHandler = (Request, Error?) -> Swift.Void
public protocol Request {
var results: [Any]? { get }
var completionHandler: RequestCompletionHandler? { get }
}
extension VNRequest: Request {}
Here, VNRequest already has a member called completionHandler that returns the following type:
public typealias VNRequestCompletionHandler = (VNRequest, Error?) -> Swift.Void
Technically, all of these types should match up, but obviously it's not a very easy scenario for the type solver to solve, so the compiler isn't too cheerful about it.
At first I thought I'd be able to refer to the original completionBlock implementation by doing the following:
extension VNRequest: Request {
public var completionHandler: RequestCompletionHandler? {
return (self as VNRequest).completionHandler
}
}
But it's not too happy about that either.
Any advice about how best to do this? I've thought about using a different name in the protocol (e.g: completionBlock_ or completionBlock$), which works, but it's a bit scrappy.
The problem occurs due to the fact that Swift is covariant in respect to closure return type, and contra-variant in respect to its arguments. Which means that (VNRequest, Error?) -> Void can't be used where (Request, Error?) -> Void is needed (the other way around is possible).
You can solve your problem by using an associated type in the Request protocol:
public protocol Request {
associatedtype RequestType = Self
var results: [Any]? { get }
var completionHandler: ((RequestType, Error?) -> Void)? { get }
}
The above protocol definition will make the VNRequest class compile, as the compiler will detect the match for RequestType.
The downside, though, is that protocols with associated types have some limitations regarding where they can be used, and also passing them as function arguments will require some where clauses to make them work.
Another alternative would be to use Self as a parameter to the completion handler:
public typealias RequestCompletionHandler<T> = (T, Error?) -> Swift.Void
public protocol Request {
var results: [Any]? { get }
var completionHandler: RequestCompletionHandler<Self>? { get }
}
This will also solve the conformance issue, but also comes with some constraints: VNRequest must be final, the functions using Request must be generic:
func send<R: Request>(_ request: R)