Sunday, February 12, 2012

Going "Native"

Codename One takes on a lofty goal: making platforms that are radically different in every way (even in their underlying programming language) act as one. We achieved that thanks to the power of Java, but its not enough...

We wanted to give developers the ability to go into the native platform to workaround any missing functionality. Obviously going to the native platform raises the level of complexity for a platform but we wanted to keep native access as simple and powerful as can be.

But first a recap on how Codename One works...
When developing on Codename One you build an application with our simulator, when you want to test on a device the build is sent to the server which generates the platform specific code. This simplified one of the hardest concepts within LWUIT and brought Codename One into the league of desktop development tools in terms of ease of use.

A question that keeps popping up is "do I need to use the build server". The answer is no but you probably would. The build server is free for use (within reasonable quotas) you could download the  various platform native ports and manually build like you did in LWUIT but this will be even harder since you will need a far more elaborate build toolchain and you will need a Mac (for iOS build) and a Windows 7 machine (for Windows Phone 7)... That's obviously a pain most of you would want to avoid.

So how would the build server allow developers to access the "native" layer (and why do I quote "native"?).
When defining "native" we set out to build something that allows access to the "true" platform specifically Dalvik on Android, MIDP+JSR's on J2ME, RIM API on Blackberry, Objective C on iOS and C# on Windows Phone 7. Hence "native" is not used in the common historical reference to C but more as the common underlying platform.

In order to unify such disparate platforms we made some restrictions which we might reduce in the future but for now all you need to do is define an interface that derives native interface e.g.:

public interface MyNativeCall extends NativeInterface {
     public String sayHello();
}

You can right click the native interface in NetBeans and it will generate stubs for all the native platforms within a directory called "native" under the project root. To access the native interface instance all you need to do is something like this:
        try {
            MyNativeCall n = (MyNativeCall)NativeLookup.create(MyNativeCall.class);
            if(n != null && n.isSupported()) {
                String s = n.sayHello();
                System.out.println("Native says: " + s);
            } else {
                System.out.println("Native interface is not supported on this platform");
            }
        } catch(Throwable t) {
            Dialog.show("Error", "Exception during native access: " + t, "OK", null);
        }

Notice several things about this example:
  • You don't need to implement the native interface, the server implements the "glue code" to generate the call to the native code.
  • The native interface has an isSupported() method that you need to implement in the native stub (more on that soon).
When building for device the source for the native code is sent together with the application jar to the server. We need to send the source for the native call since native compilation requires a rather complex toolchain (e.g. Android, xcode etc.).

I can go into all the various platform specific stubs but that is superfluous so I'll focus on Android/Dalvik which most readers should find familiar. The native stub generated for Android would look something like this:
public class MyNativeCallImpl {
     public boolean isSupported() {
          return false;
     }
     public String sayHello() {
          return null;
     }
}

Notice that the code DOES NOT implement the MyNativeCall interface (more on that soon). Just edit the generated code to this:
public class MyNativeCallImpl {
     public boolean isSupported() {
          return true;

     }
     public String sayHello() {
          return "Hello";

     }

}


And you will have your native interface working on Android. Your native interface implementation can callback into your application and reference static variables in your code. You will however, be limited by the type of variables you can transfer in order to make the code easily portable to Objective C/C (more on that in the JavaDoc's).


The reason the native implementation class doesn't implement the interface is its support for PeerComponent which allows embedding a native Component "in-place'. We have a demo in the distribution (appropriately called native demo), that demonstrates just that. When returning a peer component your interface will look like this:


public interface NativeCalls extends NativeInterface {
    public PeerComponent createNativeButton(String title);
}

And your native impl will look like this:
public class NativeCallsImpl {
    public android.view.View createNativeButton(String param) {
        android.widget.Button b = new android.widget.Button((android.content.Context)NativeDemo.getContext());
        b.setText(param);
        return b;
    }
}


Notice that the native implementation returns a platform specific UI component which is seamlessly translated to a peer component. For the thrill seekers among you here is how this code looks in Objective C, header file:
#import

@interface com_codename1_nativedemo_NativeCallsImpl : NSObject {
}

-(void*)createNativeButton:(NSString*)param;
-(BOOL)isSupported;
@end






And implementation file:
#import "com_codename1_nativedemo_NativeCallsImpl.h"
#import

@implementation com_codename1_nativedemo_NativeCallsImpl

-(void*)createNativeButton:(NSString*)param{
    UIButton *myButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [myButton setTitle:param forState:UIControlStateNormal];
    return myButton;
}


-(BOOL)isSupported{
    return YES;
}

@end


0 comments:

Post a Comment