2

In my Kotlin multiplatform project i heavily use namespaces. Also since i'm using MVP i have similar classnames. eg:

com.company.project.usecase1.Model
com.company.project.usecase1.View
com.company.project.usecase1.Presenter
com.company.project.usecase2.Model
com.company.project.usecase2.View
com.company.project.usecase2.Presenter

Now i want to use it in iOS app (written in Swift). So i've added iOS target to build.gradle - everything like in example. I was able to generate Cocoapod (gradlew podspec) and use it Swift app.

Related part of build.gradle:

version = "$rootProject.module_version"

kotlin {
    cocoapods {
        summary = "App MVP of NotesClientApp"
        homepage = "some url"
    }
}

However since Swift does not have namespaces and classnames look similar generated obj-c wrapper looks ugly: it uses artificial mutations (underline) in classnames just to distinguish the names, eg.

__attribute__((swift_name("Presenter__")))
@protocol App_mvpPresenter__ <App_mvpBasePresenter>
@required
@end;

__attribute__((swift_name("View__")))
@protocol App_mvpView__ <App_mvpBaseView>
@required
- (void)showValidationErrorError:(App_mvpKotlinException *)error __attribute__((swift_name("showValidationError(error:)")));
- (void)showNotesList __attribute__((swift_name("showNotesList()")));
@property NSString *host __attribute__((swift_name("host")));
@property NSString *port __attribute__((swift_name("port")));
@end;

__attribute__((swift_name("Model__")))
@interface App_mvpModel__ : KotlinBase
- (instancetype)initWith_host:(NSString * _Nullable)_host _port:(App_mvpUInt * _Nullable)_port __attribute__((swift_name("init(_host:_port:)"))) __attribute__((objc_designated_initializer));
- (void)updateHost:(NSString *)host port:(uint32_t)port __attribute__((swift_name("update(host:port:)")));
@property (readonly) NSString * _Nullable host __attribute__((swift_name("host")));
@property (readonly) App_mvpUInt * _Nullable port __attribute__((swift_name("port")));
@property id<App_mvpPresenter__> _Nullable presenter __attribute__((swift_name("presenter")));
@end;

__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("PresenterImpl__")))
@interface App_mvpPresenterImpl__ : KotlinBase <App_mvpPresenter__>
- (instancetype)initWithModel:(App_mvpModel__ *)model __attribute__((swift_name("init(model:)"))) __attribute__((objc_designated_initializer));
- (void)attachViewView:(id<App_mvpView__>)view __attribute__((swift_name("attachView(view:)")));
- (void)onModelChanged __attribute__((swift_name("onModelChanged()")));
- (void)onViewChanged __attribute__((swift_name("onViewChanged()")));
- (void)onViewDetached __attribute__((swift_name("onViewDetached()")));
@property (readonly) App_mvpModel__ *model __attribute__((swift_name("model")));
@end;

I guess there should be some possibility to adjust the behaviour: add some config file or annotations for naming adjustments.

Any other possibility to rename classes in Obj-c/Swift not related to Kotlin? i've tried to use Swift typealiases, but got "typealias invalid redeclaration" error only.

Any thoughts?

PS. Also i can see module name (app-mvp) works as prefix, eg. classname App_mvpView__ - any possibility to adjust generated Framework name without changing of Gradle module name (since i still want to use proper JVM build artifact names: app-mvp-jvm.jar)?

PPS. I do understand it could be easier just rename classes to make them unique in all namespaces, but anyway.

4ntoine
  • 19,816
  • 21
  • 96
  • 220

1 Answers1

2

At this time it isn't possible to override the class names for native. JavaScript MPP has an annotation called JSName so I wouldn't rule it out in the future. To change your framework name you can simply set the baseName value in your framework configuration under targets.

    fromPreset(iOSTarget, 'ios') {
        binaries {
            framework {
                baseName = "MyFrameworkName"
            }
        }
    }

If you're using the packForXcode task you will likely need to update it to find your new framework. Mine looks like this:

task packForXCode(type: Sync) {
    final File frameworkDir = new File(buildDir, "xcode-frameworks")
    final String mode = (project.findProperty("XCODE_CONFIGURATION")?.toUpperCase() ?: 'DEBUG').split("_").first()


    inputs.property "mode", mode
    def bin = kotlin.targets.ios.compilations.main.target.binaries.findFramework("", mode)
    dependsOn bin.linkTask

    from bin.outputDirectory
    into frameworkDir

    doLast {
        new File(frameworkDir, 'gradlew').with {
            text = "#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '${rootProject.rootDir}'\n./gradlew \$@\n"
            setExecutable(true)
        }
    }
}

Mine also has a customization to support multiple project schemes in Xcode. I have one for each environment (dev, test, prod) the app can point to.

Sam Corder
  • 5,374
  • 3
  • 25
  • 30
  • Does it work for CocoaPods (not Framework approach)? BTW now suggested target syntax looks a bit different: `iosArm32()` (../`iosArm64`/`iosX64`) instead of `fromPreset(iOSTarget, 'ios')`, but thanks for pointing. – 4ntoine Jul 19 '19 at 04:21
  • i've checked it - it does not work: having cocoapods plugin hurts - targets are created by it and i can change frameworks (debug + release) baseNames (with `targets.iosX64.binaries .findAll { it instanceof org.jetbrains.kotlin.gradle.plugin.mpp.Framework } .every { it.baseName = "AppMvp" }`), but not for cocoapod. In podspec file `spec.name` is still `app_mvp` – 4ntoine Jul 19 '19 at 12:28
  • probably it can be done like that: https://github.com/JetBrains/kotlin-native/issues/3208#issuecomment-514599882 – 4ntoine Jul 24 '19 at 13:37