Can't make using iOS protocol work in NativeScript - ios

I am creating a NativeScript plugin that will work with my iOS and Android SDKs. In my iOS code, I have an optional #protocol that contains a few optional methods with callbacks about the state of the SDK. I am now trying to access this protocol in NativeScript, and I tried searching the internet and following the guide here, but unfortunately, I still have issues with it. Here is a part of the #protocol in iOS:
#protocol BLASessionDelegate <NSObject>
#optional
- (void)sessionInitialized;
...
Here is the code I put in the "index.d.ts" in my plugin:
export interface SessionDelegate {
sessionInitialized?();
...
}
Here is the part in the "bla.ios.d.ts" file in my plugin:
declare interface BLASessionDelegate extends NSObjectProtocol {
sessionInitialized?();
}
#NativeClass()
declare class BLASessionDelegateImpl extends NSObject implements SessionDelegate {
static ObjCProtocols = [BLASessionDelegate];
static new(): BLASessionDelegateImpl {
return <BLASessionDelegateImpl>super.new(); // calls new() on the NSObject
}
sessionDelegate: SessionDelegate;
public sessionInitialized() {
console.log("Adi - inside sessionInitialized");
if(this.sessionDelegate.sessionInitialized !== undefined) {
this.sessionDelegate.sessionInitialized();
}
}
}
Here is the part in the "bla.ios.ts" file in my plugin:
export const setSessionDelegate = function(sessionDelegate: SessionDelegate) {
if(typeof(bla) !== "undefined") {
console.log("Adi - in the setSessionDelegate");
let delegate: BLASessionDelegateImpl = BLASessionDelegateImpl.new();
delegate.sessionDelegate = sessionDelegate;
bla.setSessionDelegate(delegate);
}
}
This is the code I put in the test app in the "app.ts" file:
let delegate: bla.SessionDelegate = {
sessionInitialized: function() {
const sessionLink: string = bla.generateSessionLink();
console.log("Adi - sessionLink = ", sessionLink);
}
}
bla.setSessionDelegate(delegate);
I am still getting the following error with a crash when running the app and I can't figure out why:
***** Fatal JavaScript exception - application has been terminated. *****
NativeScript encountered a fatal error: Uncaught ReferenceError: BLASessionDelegateImpl is not defined
at
setSessionDelegate(file: app/webpack:/myCoolApp/work/native-script-plugin/native-script/src/bla.ios.ts:165:41)
at ./app/app.ts(file: app/webpack:/myCoolApp/app/app.ts:18:0)
at __webpack_require__(file: app/webpack:/myCoolApp/webpack/bootstrap:24:0)
at __webpack_exec__(file:///app/bundle.js:839:39)
at (file:///app/bundle.js:840:318)
at __webpack_require__.X(file: app/webpack:/myCoolApp/webpack/runtime/startup entrypoint:6:0)
at (file:///app/bundle.js:840:47)
at (file:///app/bundle.js:845:3)
at require(:1:137)

Related

React Native iOS native module pass parameters

I am writing an NPM library that contains iOS & Android native modules. Important is that I need to pass parameters to the native module before startup. This works great for Android:
package ...
import com.facebook.react.bridge.*
class MyNativeModule(reactContext: ReactApplicationContext, parameter: String) {
override fun getName(): String {
return "MyModule"
}
#ReactMethod
fun retrieveParameter(promise: Promise) {
promise.resolve(parameter)
}
}
When turning off autolinking a library user can just create their own RN package and use the following to set the parameter:
class MyAppRNPackage(private val voizeCore: VoizeCore = VoizeCore.getInstance()) : ReactPackage {
override fun createNativeModules(reactApplicationContext: ReactApplicationContext): List<NativeModule> {
return arrayListOf<NativeModule>(
MyNativeModule(reactApplicationContext, "this is the parameter"),
)
}
override fun createViewManagers(reactApplicationContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}
}
In iOS the native module would look something like this:
// MyNativeModule.m
#import "MyNativeModule.h"
#implementation MyNativeModule
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(retrieveParameter:(RCTPromiseResolveBlock)resolve rejecter: (RCTPromiseRejectBlock)reject)
{
resolve(???)
}
#end
But how is it possible to set a parameter for the iOS module? The problem is that in iOS, native modules can not be registered manually. Furthermore, only classes are registered not class instances.
Any idea how you could pass parameters from native code to the iOS native module?

Create NativeScript Plugin using IOS Static Library issue

I have created .a static library (tested in Xcode for native ios project and Its working fine)
Now I am following this https://github.com/NativeScript/nativescript-plugin-seed to create nativescript plugin using .a static framework.
Plugin structure
module.modulemap file is created by me and it's look like this
module libstaticlibrary {
umbrella header "staticlibrary.h"
export *
}
staticlibrary.h
#import <Foundation/Foundation.h>
#interface staticlibrary : NSObject
+ (NSString *)sayHello;
#end
libstaticlibrary.d.ts also created by me
declare class staticlibrary extends NSObject {
static sayHello():string;
}
Then in helloplugin.common.ts I am trying to access staticlibrary.sayHello() method.
export class Utils {
public static SUCCESS_MSG(): string {
// let msg = `Your plugin is working on ${app.android ? 'Android' : 'iOS'}.`;
let msg = staticlibrary.sayHello();
setTimeout(() => {
dialogs.alert(`${msg} For real. It's really working :)`).then(() => console.log(`Dialog closed.`));
}, 2000);
return msg;
}
I am getting following error.
node_modules/nativescript-helloplugin/helloplugin.common.ts(21,15): error TS2304: Cannot find name 'staticlibrary'.
What is I am doing wrong here?
It's just the TypeScript compiler error, you have to generate typings for your static library (refer docs to know how) or just add this line at top of your file.
declare var staticlibrary: any
I see that you do have a declaration file in your code snippet, if you want to use it you have to include it to your references.d.ts file.

Typhoon parameter injection with initalizer crashing app

i want to use Typhoon (GitHub & WebSite) for dependency injection in my app. I use Swift Version 3 and Typhoon 3.6. Unfortunately my app is crashing when I try to initalize an object. I have the following protocol:
Protocol
import Foundation
#objc public protocol Client {
func method()
}
Protocol implementation
import Foundation
public class ClientWhateverImpl : NSObject, Client{
let name : String
init(name: name) {
self.name = name
}
public func method(){
//make something
}
}
Assembly
import Foundation
import Typhoon
public class MyAssembly: TyphoonAssembly {
public dynamic func client() -> AnyObject {
return TyphoonDefinition.withClass(ClientWhateverImpl.self) {
(definition) in
definition!.useInitializer("initWithName:") {
(initializer) in
initializer!.injectParameter(with: "name")
}
} as AnyObject
}
}
Call it somewhere
let myAssembly : MyAssembly = MyAssembly()
myAssembly.activate()
let client = myAssembly.client()
Unfortunately I got the following error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Method 'initWithName:' not found on 'MyApp.ClientWhateverImpl'. Did you include the required ':' characters to signify arguments?'
I read some posts on stackoverflow about this error but on their side they forget to use the objectice-c method syntax. But in my case I use the objc method "initWithName". Is there something different in swift 3? Has someone the same problem?
Ok. I found the issue. It has something to do with an object which i wanted to inject. It doesn't inherit from NSObject and Typhoon makes something with it and fails:
definition!.useInitializer("initWithObject:") {
(initializer) in
initializer!.injectParameter(with: MyObject())
}
Before:
public class MyObject{
}
Solution:
public class MyObject: NSObject{
}
The documentation even says it:
Every class you want to inject has to be a subclass of NSObject in
some way (either by subclassing or adding #objc modifier).
I just thought the ClientWhateverImpl in my case has to inherit from NSObject. My fault. This is question closed

Why am I getting ObjCClassNotFoundException: GADInterstitial in RoboVM?

I am implementing admob in my iOS module using this guide.
I have a view controller where my interstitial ads are initialized like this
#CustomClass
public class ViewController extends UIViewController implements ActionResolver {
private GADInterstitial interstitial;
private static final String AD_UNIT_ID = "ca-app-pub-XXXXXXXXXXXXXXXX-XXXXXXXXXX";
public ViewController() {
viewDidLoad();
}
#Override
public void viewDidLoad() {
super.viewDidLoad();
interstitial = createAndLoadInterstitial();
}
private GADInterstitial createAndLoadInterstitial() {
//Ad Unit ID of your interstital, from adMob account.
GADInterstitial interstitial = new GADInterstitial(AD_UNIT_ID);
System.out.println("Add unit ID is " + AD_UNIT_ID);
interstitial.setDelegate(new GADInterstitialDelegateAdapter() {
#Override
public void didDismissScreen(GADInterstitial ad) {
ViewController.this.interstitial = createAndLoadInterstitial();
}
});
interstitial.loadRequest(createRequest());
return interstitial;
}
private GADRequest createRequest() {
GADRequest request = new GADRequest();
// To test on your devices, add their UDIDs here:
request.setTestDevices(Arrays.asList(GADRequest.getSimulatorID()));
return request;
}
#IBAction
public void showInterstitialAd() {
if (interstitial.isReady()) {
interstitial.present(UIApplication.getSharedApplication().getKeyWindow().getRootViewController());
System.out.println("Interstitial is loaded.");
} else {
interstitial.loadRequest(createRequest());
System.out.println("Interstitial not ready!");
}
}
}
My implementation is similar to this guide also.
When I run my app on the simulator I get the following exception.
org.robovm.objc.ObjCClassNotFoundException: GADInterstitial
at org.robovm.objc.ObjCClass.getByType(ObjCClass.java:251)
at org.robovm.objc.ObjCClass.getFromObject(ObjCClass.java:212)
at org.robovm.objc.ObjCObject.getObjCClass(ObjCObject.java:161)
at org.robovm.apple.foundation.NSObject.alloc(NSObject.java:214)
at org.robovm.objc.ObjCObject.<init>(ObjCObject.java:79)
at org.robovm.apple.foundation.NSObject.<init>(NSObject.java:157)
at org.robovm.pods.google.mobileads.GADInterstitial.<init>(GADInterstitial.java:52)
at com.nauv.fut.simulator.ViewController.createAndLoadInterstitial(ViewController.java:39)
at com.nauv.fut.simulator.ViewController.viewDidLoad(ViewController.java:34)
at com.nauv.fut.simulator.ViewController.<init>(ViewController.java:27)
at com.nauv.fut.simulator.IOSLauncher.createApplication(IOSLauncher.java:30)
at com.badlogic.gdx.backends.iosrobovm.IOSApplication$Delegate.didFinishLaunching(IOSApplication.java:65)
at com.badlogic.gdx.backends.iosrobovm.IOSApplication$Delegate.$cb$application$didFinishLaunchingWithOptions$(IOSApplication.java)
at org.robovm.apple.uikit.UIApplication.main(Native Method)
at org.robovm.apple.uikit.UIApplication.main(UIApplication.java:413)
at com.nauv.fut.simulator.IOSLauncher.main(IOSLauncher.java:35)
I have imported the admob framework on my ios build.gradle. I have also tried moving it to ios module in main build.gradle though this doesnt change anything. How can I fix this error? My class is being imported correctly and I am using project.ext.robopodsVersion = "1.14.0" on my app build.gradle.
Fortunately some people forked the project and I have found a working solution :)
The fork : https://github.com/MobiDevelop/robovm-robopods
I did the following.
Gradle:
Build script dependency
dependencies {
...
classpath 'com.mobidevelop.robovm:robovm-gradle-plugin:2.2.0-SNAPSHOT'
}
IOS Project dependencies:
...
compile "com.mobidevelop.robovm:robopods-google-mobile-ads-parent:2.2.0-SNAPSHOT"
compile "com.mobidevelop.robovm:robopods-google-mobile-ads-ios:2.2.0-SNAPSHOT"
compile "com.mobidevelop.robovm:robovm-rt:2.2.0-SNAPSHOT"
compile "com.mobidevelop.robovm:robovm-cocoatouch:2.2.0-SNAPSHOT"
Download SDK from here
Copy the GoogleMobileAds.framework folder in ios/libs
Modify the robovm.xml file (I went completely overkill on the frameworks list but I was tired of compilation error, if you find the ones that are not necessary, feel free to let me know):
<config>
...
<frameworkPaths>
<path>libs</path>
</frameworkPaths>
<frameworks>
<framework>UIKit</framework>
<framework>OpenGLES</framework>
<framework>QuartzCore</framework>
<framework>CoreGraphics</framework>
<framework>OpenAL</framework>
<framework>AudioToolbox</framework>
<framework>AVFoundation</framework>
<framework>AdSupport</framework>
<framework>AudioToolbox</framework>
<framework>CoreMedia</framework>
<framework>CoreMotion</framework>
<framework>CoreVideo</framework>
<framework>CoreTelephony</framework>
<framework>CoreBluetooth</framework>
<framework>SafariServices</framework>
<framework>EventKit</framework>
<framework>EventKitUI</framework>
<framework>MediaPlayer</framework>
<framework>MessageUI</framework>
<framework>StoreKit</framework>
<framework>SystemConfiguration</framework>
<framework>GoogleMobileAds</framework>
<framework>VideoToolbox</framework>
<framework>GLKit</framework>
</frameworks>
</config>
And this works (for me)

"initialize" class method for classes in Swift?

I'm looking for behavior similar to Objective-C's +(void)initialize class method, in that the method is called once when the class is initialized, and never again thereafter.
A simple class init () {} in a class closure would be really sleek! And obviously when we get to use "class vars" instead of "static vars in a struct closure", this will all match really well!
If you have an Objective-C class, it's easiest to just override +initialize. However, make sure subclasses of your class also override +initialize or else your class's +initialize may get called more than once! If you want, you can use dispatch_once() (mentioned below) to safeguard against multiple calls.
class MyView : UIView {
override class func initialize () {
// Do stuff
}
}
If you have a Swift class, the best you can get is dispatch_once() inside the init() statement.
private var once = dispatch_once_t()
class MyObject {
init () {
dispatch_once(&once) {
// Do stuff
}
}
}
This solution differs from +initialize (which is called the first time an Objective-C class is messaged) and thus isn't a true answer to the question. But it works good enough, IMO.
There is no type initializer in Swift.
“Unlike stored instance properties, you must always give stored type properties a default value. This is because the type itself does not have an initializer that can assign a value to a stored type property at initialization time.”
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks.
You could use a type property which default value is a closure. So the code in the closure would be executed when the type property (or class variable) is set.
class FirstClass {
class var someProperty = {
// you can init the class member with anything you like or perform any code
return SomeType
}()
}
But class stored properties not yet supported (tested in Xcode 8).
One answer is to use static, it is the same as class final.
Good link for that is
Setting a Default Property Value with a Closure or Function
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks.
Code example:
class FirstClass {
static let someProperty = {
() -> [Bool] in
var temporaryBoard = [Bool]()
var isBlack = false
for i in 1...8 {
for j in 1...8 {
temporaryBoard.append(isBlack)
isBlack = !isBlack
}
isBlack = !isBlack
}
print("setting default property value with a closure")
return temporaryBoard
}()
}
print("start")
FirstClass.someProperty
Prints
start
setting default property value with a closure
So it is lazy evaluated.
For #objc classes, class func initialize() definitely works, since +initialize is implemented by the Objective-C runtime. But for "native" Swift classes, you'll have to see the other answers.
You can use stored type properties instead of initialize method.
class SomeClass: {
private static let initializer: Void = {
//some initialization
}()
}
But since stored types properties are actually lazily initialized on their first access, you will need refer them somewhere. You can do this with ordinary stored property:
class SomeClass: {
private static let initializer: Void = {
//some initialization
}()
private let initializer: Void = SomeClass.initializer
}
#aleclarson nailed it, but as of recent Swift 4 you cannot directly override initialize. You still can achieve it with Objective-C and categories for classes inheriting from NSObject with a class / static swiftyInitialize method, which gets invoked from Objective-C in MyClass.m, which you include in compile sources alongside MyClass.swift:
# MyView.swift
import Foundation
public class MyView: UIView
{
#objc public static func swiftyInitialize() {
Swift.print("Rock 'n' roll!")
}
}
# MyView.m
#import "MyProject-Swift.h"
#implementation MyView (private)
+ (void)initialize { [self swiftyInitialize]; }
#end
If your class cannot inherit from NSObject and using +load instead of +initialize is a suitable fit, you can do something like this:
# MyClass.swift
import Foundation
public class MyClass
{
public static func load() {
Swift.print("Rock 'n' roll!")
}
}
public class MyClassObjC: NSObject
{
#objc public static func swiftyLoad() {
MyClass.load()
}
}
# MyClass.m
#import "MyProject-Swift.h"
#implementation MyClassObjC (private)
+ (void)load { [self swiftyLoad]; }
#end
There are couple of gotchas, especially when using this approach in static libraries, check out the complete post on Medium for details! ✌️
I can't find any valid use case to have something like +[initialize] in Swift. Maybe this explains way it does not exist
Why do we need +[initialize] in ObjC?
To initialize some global variable
static NSArray *array;
+ (void)initialize {
array = #[1,2,3];
}
which in Swift
struct Foo {
static let array = [1,2,3]
}
To do some hack
+ (void)initialize {
swizzle_methodImplementation()
}
which is not supported by Swift (I can't figure out how to do it for pure Swift class/struct/enum)

Resources