TrustWalletCore/WalletCore pod in Kotlin Multiplatform - almost no classes - ios

In our Kotlin Mobile Multiplatform project for iOS and Android, we're trying to access TrustWalletCore cocoapod from Kotlin.
// build.gradle.kts (:shared)
cocoapods {
version = "1.0"
podfile = project.file("../iosApp/Podfile")
pod("WalletCore")
}
And the Podfile is
target 'iosApp' do
pod 'TrustWalletCore'
end
This successfully enables import cocoapods.WalletCore.* in shared/iosMain - without the above cocoapods {...} the import is unavailable.
However, only a Crypto class is available from this package (and CryptoMeta which doesn't look too different).
By the looks of it, it's generated from the Pod/library by commonizer in 0_WalletCore.knm (about 15 expect functions in total - a couple here for illustration):
#kotlin.commonizer.ObjCCallable public open external expect fun base58Encode(data: platform.Foundation.NSData): kotlin.String { /* compiled code */ }
#kotlin.commonizer.ObjCCallable public open external expect fun generateMnemonicFromSeed(seed: platform.Foundation.NSData): kotlin.String { /* compiled code */ }
It has mnemonic-related functionality, as well as signHash/verifySignature but not much else.
I was hoping to see - available to import in Kotlin - classes like HDWallet, EthereumSigningInput etc.
I can use these library classes in Swift, via pod TrustWalletCore in Xcode (import WalletCore).
WHY can I not get a similar/full set of classes via native.cocoapods plugin?

Try to declare dependency with moduleName parameter:
kotlin {
cocoapods {
...
pod(name = "TrustWalletCore", version = "3.1.0", moduleName = "WalletCore")
}
}

Related

Aar containing string resources aren't visible in a jetpack compose project

I'm having an issue with an aar in a jetpack compose project.
I've created a library which contains all the string resources I'm using in multiple projects. In my old java/kotlin projects there is no issue and I can implement the library and reach the strings. But when I do this in my new jetpack compose project it loads the aar but when in code I do R.string. I don't see the specific strings.
Also when I add some kotlin classes with functions in the library, these functions can be accessed. This way I'm 100% sure the library is loaded.
Is there a way to solve this?
The android manifest for the library looks like:
<manifest package="com.test.library"/>
The build.gradle:
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdk 31
defaultConfig {
minSdk 21
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
android.libraryVariants.all { variant ->
def debug = ""
if (variant.name == "debug") {
debug = "-debug"
}
variant.outputs.all {
outputFileName = "TestLibrary" + debug + ".aar"
}
}
}
}
}
dependencies {
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
In my jetpack compose project I implement it with the following call:
implementation files('libs/TestLibrary.aar')
How can I solve this? Is there somebody with the same issue (and a solution)?
Kind regards,
Jeroen
I also have been struggling with this for a while and I believe I found what causes this issue.
Your jetpack compose project should have a new entry in its gradle.properties file. The entry should look like this:
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
If you change this value to false your project should be able to find the string resources declared in your library. As the description shows.
So your new entry will look like this:
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=false
Hope this helps!

Nested multiplatform libraries usage with cocoapods intergration

In Android world, I could add a dependency in a module, for any module which add this module as a dependency, it could use API from that dependency as will. e.g. facebook sdk -> utils module -> foo module -> main app module, both foo module & main app module could use facebook sdk API as well.
How do I did the same thing in multiplatform world (or in iOS world)?
facebook iOS sdk pod -> utils module -> foo module -> iOS main project
I've tried to add pod only in utils module, but neither foo & main could access facebook iOS API. If I add same pod in foo module, it can't access Facebook API neither.
Or should I use another dependency management tool other than cocoapods?
I guess you would like to have something like Export dependencies to binaries in the end. However, I'm not sure this will work for dependencies between Kotlin modules. For dependencies between them, I would recommend using the api dependencies link and adding pods for each module individually.
I mean, core module's build.gradle.kts should contain only
...
kotlin {
ios()
cocoapods {
ios.deploymentTarget = "13.5"
summary = "CocoaPods test library"
homepage = "https://github.com/JetBrains/kotlin"
pod("FBSDKCoreKit")
}
}
...
login module is having both cocoapods and core dependencies:
...
kotlin {
ios()
framework {
// Mandatory properties
// Configure fields required by CocoaPods.
summary = "Some description for a Kotlin/Native module"
homepage = "Link to a Kotlin/Native module homepage"
// Framework name configuration. Use this property instead of deprecated 'frameworkName'
baseName = "MyFramework"
// Optional properties
// (Optional) Dynamic framework support
isStatic = false
// (Optional) Dependency export
export(project(":core"))
transitiveExport = true
// (Optional) Bitcode embedding
embedBitcode(BITCODE)
}
pod("FBSDKCoreKit")
pod("FBSDKLoginKit")
}
...
sourceSets {
val iosMain by getting {
dependencies {
api(project(":core"))
}
}
}
}
...
You can only export a single Kotlin Xcode Framework. It's kind of a "big binary" that has all of it's dependencies. If "utils module" and "foo module" are both Kotlin modules, you should collect them into a single Kotlin Xcode Framework.

Xcode shows swift compiler errors when importing cocoapods generated by gradle plugin

I am trying to use shared code in my iOS project from cocoapods that i created with cocoapods gradle plugin.
Podspec is created without problems.
My shared build.gradle:
plugins {
id("org.jetbrains.kotlin.multiplatform")
id("com.android.library")
id ("org.jetbrains.kotlin.native.cocoapods")
}
android {
compileSdkVersion 29
buildToolsVersion '30.0.0'
defaultConfig {
minSdkVersion 19
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
version = "1.0"
kotlin {
ios()
android()
cocoapods {
// Configure fields required by CocoaPods.
summary = "Some description for a Kotlin/Native module"
homepage = "Link to a Kotlin/Native module homepage"
// The name of the produced framework can be changed.
// The name of the Gradle project is used here by default.
frameworkName = "toshlShared"
}
sourceSets {
commonMain.dependencies {
api 'org.jetbrains.kotlin:kotlin-stdlib-common'
}
androidMain.dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
}
iosMain.dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
}
}
}
When i run pod install all looks good, but when i run ios app i see this error:
EDIT: Added my project folder structure with my single .kt file (it is accessible from android code)
As far as I can see, the problem here is caused by the project structure. In the Gradle script, there are three targets declared: an Android one, and iosX64+iosArm64 shortcut(see details here) providing the other two. For this layout, there would be two compilations per iOS target(iosX64Main, iosX64Test, iosArm64Main, iosArm64Test) and
a compilation per Android build variant, for Android targets;
according to the documentation.In this particular case, only source file SharedUtils.kt is being located in a directory located on an Android main compilation's source set. This means it does not get into any iOS compilation. To make this source file shared, it should be relocated to the common code. There are two default source sets for common code in Kotlin Multiplatform: commonMain and commonTest. Therefore, source code should be placed under src/<sourceSetName>/kotlin directory(commonMain for this case).

Declare Podspec with a dependency to an objc library

I am developing a Swift framework (MyFramework) that has a dependency to a third party static library written in Objc (NewRelic)
I am using CocoaPods 1.4.0 and I have declared my podspec like this:
...
s.source_files = 'MyFrame/Classes/**/*'
s.static_framework = true
s.dependency 'NewRelicAgent', '6.1.1' # Obj-c
s.xcconfig = { 'HEADER_SEARCH_PATHS' => '$(PODS_ROOT)/NewRelicAgent/NewRelicAgent/NewRelicAgent.framework/Headers', 'SWIFT_INCLUDE_PATHS' => '$(PODS_TARGET_SRCROOT)/MyFrameFramework' }
s.preserve_paths = 'MyFrameFramework/MyFrame.modulemap'
From what I read in different posts, what I think I am doing here is:
first declare a static_framework to be able to use a static library, NewRelic.
Set the NewRelic dependency.
Set the header paths to find the NewRelic headers and locate the modulemap as explained here
This is my MyFrame.modulemap:
framework module LGResources {
umbrella header "MyFrameFramework.h"
export *
module * { export * }
}
And this MyFrameFramework.h:
//! Project version number for LGResources.
FOUNDATION_EXPORT double MyFrameVersionNumber;
//! Project version string for LGResources.
FOUNDATION_EXPORT const unsigned char MyFrameVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <Amplitude/PublicHeader.h>
#import <NewRelicAgent/NewRelic.h>
Inside my framework classes I am trying to use NewRelic but it does not work
I am starting to give up since I find confusing to have so many references in the CocoaPod specs: module_map, frameworks, vendored_frameworks, dependency, preserve_paths, ...
Can someone please point me into the right direction? How can I declare a static dependency and use it inside my framework?
Many thanks in advance!
PS: I don't really know where should I put the MyFrameFramework.h file to be managed by CocoaPods, by I have tried different manual approaches with no luck.
There may be additional issues, but one problem is that NewRelicAgent.framework is missing a module map.
Static frameworks can have other static frameworks or static vendored_frameworks as dependencies. A static library is not sufficient. A module map bundled into a framework is necessary to tell the build system how to access its public methods from Swift or Objective C modules.
The NewRelicAgent podspec is specifying a vendored_framework, but the zip is missing a module map.
It might be possible to come up with a workaround, but the best solution would be to convince the NewRelicAgent pod maintainers to update the pod.

How to build Unity3d Plugin for iOS

I have a very tiny Objective-C library built for iOS and I want to export it to Unity. I understand the basic process of writing a csharp wrapper that marshals all the invocations to native library, but I completely have no idea where to start. Could anyone please explain step-by-step how to create a unity package with my library so I could also distribute it to other developers.
Unity3d documentation is pretty brief and does not explain anything.
Thanks.
Okay, after playing few days with Unity3d on Mac I finally figured it out. All the code in this guide is dummy. I have written this stuff in 15 minutes or so, so don't be bothered by mistakes and typos.
1) Open Unity, create new project (File -> New Project) and save it somewhere
2) When the project is generated it has the following structure:
ProjectName/Assets (That's what you need)
ProjectName/Library (Nevermind what's there)
ProjectName/ProjectSettings (You don't care about it)
ProjectName/ProjectName.sln (MonoDevelop project)
3) Go to ProjectName/Assets and create the following folders: Plugins/iOS, so in the end you'll have a folder structure like this: ProjectName/Assets/Plugins/iOS
4) Put your compiled library (.a) file and necessary headers inside of ProjectName/Assets/Plugins/iOS or copy the source code of your library there (.mm, .h, .m, etc..). Remember, normally you can only access C-functions from C#, so you'll have to wrap your Objective-C stuff in C-code somehow, in my case all Objective-C objects were implemented in a form of Singleton so it wasn't hard to make a C-style wrapper around, for instance:
CWrapper.h:
extern "C" void MySDKFooBarCFunction();
CWrapper.mm
#import "CWrapper.h"
#import "MyObjectiveCLibrary.h" // your actual iOS library header
void MySDKFooBarCFunction() {
[MyObjectiveCLibrary doSomeStuff];
}
5) Then go to ProjectName/Assets and create a folder for CSharp wrapper class(es), call it whatever you want, for example: ProjectName/Assets/MySDK
6) Inside of MySDK folder create MySDK.cs file, the dummy example of C# wrapper would look like this:
using UnityEngine;
using System;
using System.Runtime.InteropServices;
public class MySDK
{
// import a single C-function from our plugin
[DllImport ("__Internal")]
private static extern void MySDKFooBarCFunction();
// wrap imported C-function to C# method
public static void FooBarCFunction() {
// it won't work in Editor, so don't run it there
if(Application.platform != RuntimePlatform.OSXEditor) {
MySDKFooBarCFunction();
}
}
}
7) Create a shell script to pack this stuff into .unitypackage and put it next to your project folder (not inside). Adjust EXPORT_PATH and PROJECT_PATH variables in the script for your needs.
#!/bin/sh
WORKDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
UNITY_BIN="/Applications/Unity/Unity.app/Contents/MacOS/Unity"
EXPORT_PATH="${WORKDIR}/ProjectName.unitypackage"
PROJECT_PATH="${WORKDIR}/ProjectName"
ASSETS_PATH="Assets"
$UNITY_BIN -batchmode -quit \
-logFile export.log \
-projectPath $PROJECT_PATH \
-exportPackage $ASSETS_PATH $EXPORT_PATH
8) Run the created bash script to get your package build. All stuff from Assets will be included in XCode project for your Unity Project when you generate it via File -> Build Settings in Unity Editor. You can use generated package to distribute your code to other developers so they can simply include your library to their Unity projects by double clicking on the package file.
Don't forget to shutdown Unity Editor when you run this script, otherwise it may fail to build a package.
If you have some issues and package does not show up, this script always prints log to export.log
Next steps make sense only if you want to make a Demo unity project for your library (good for testing at least)
9) You can put created Unity project (ProjectName.unity) to Assets/MySDKDemo so you have a demo inside of your package.
10) Create a simple script for your Demo Unity3d scene at Assets/MySDKDemo/MySDKDemo.cs, for example:
using UnityEngine;
using System;
using System.Collections;
public class MySDKDemo : MonoBehaviour
{
private GUIStyle labelStyle = new GUIStyle();
private float centerX = Screen.width / 2;
// Use this for initialization
void Start ()
{
labelStyle.fontSize = 24;
labelStyle.normal.textColor = Color.black;
labelStyle.alignment = TextAnchor.MiddleCenter;
}
void OnGUI ()
{
GUI.Label(new Rect(centerX - 200, 20, 400, 35), "MySDK Demo", labelStyle);
if (GUI.Button(new Rect(centerX - 75, 80, 150, 35), "DoStuff"))
{
MySDK.FooBarCFunction();
}
}
}
11) Go to Unity Editor. Find the "Main Camera" in left sidebar in Unity Editor, select it and in the bottom of Inspector panel (right sidebar) click on AddComponent, select Scripts -> MySDKDemo script
12) Build the XCode project and run on device.
Few notes
1) Plugins don't work in Unity Editor, simply because they're not compiled in the real-time, well, not sure but probably until you use C# in your plugins, probably C# stuff gets linked immidiately and works in Editor environment.
2) This post does not cover marshaling, or data/memory management between native <-> managed code, as it is very well documented.
Interop with Native Libraries # Mono project
3) Callbacks from C# to C can be passed using C# delegates, on C-side you use standard functions declarations, on C# side you declare delegates with the same signature. It seems that booleans, integers and strings (C: char*) are marshalled flawlessly (I don't talk about memory management policy and who's responsible to release memory or return value policies).
However it will not work on iOS builds out-of-box due to platform limitations, but C#-to-C callbacks still can be implemented using MonoPInvokeCallbackAttribute, useful links on this topic:
Reverse Callbacks # Xamarin Docs
MonoPInvokeCallbackAttribute example # Xamarin Forums
Actually in Unity 4 there's AOT.MonoPInvokeCallbackAttribute already implemented, it's limited to static delegates that can be passed to unmanaged code, but still better than nothing.
4) There's a way to get Unity RootViewController using UnityGetGLViewController function. Just declare this function in your implementation file, i.e.:
extern UIViewController *UnityGetGLViewController();
And use UnityGetGLViewController() whenever you need to get an access to RootViewController.
5) There's much more magic and ugly stuff in details, keep your C interfaces as simple as possible otherwise marshalling can become your nightmare and also keep in mind that managed-to-unmanaged is generally expensive.
6) You definitely use some frameworks in your native code and you don't want linker problems. For example, if you use Keychain in your library then you need to include Security.framework into Xcode project.
I suggest to give a try to XUPorter, it helps Unity to integrate any additional dependencies into Xcode project.
Good luck!

Resources