Phone Functions

Most of the low level phone functionality is accessible in the Display class. Think of it as a global central class covering your access to the "system".

SMS

Codename One supports sending SMS messages but not receiving them as this functionality isn’t portable. You can send an SMS using:

Display.getInstance().setSMS("+999999999", "My SMS Message");

Android/Blackberry support sending SMS’s in the background without showing the user anything. iOS & Windows Phone just don’t have that ability, the best they can offer is to launch the native SMS app with your message already in that app. Android supports that capability as well (launching the OS native SMS app).

The default sendSMS API ignores that difference and simply works interactively on iOS/Windows Phone while sending in the background for the other platforms.

The getSMSSupport API returns one of the following options:

  • SMS_NOT_SUPPORTED - for desktop, tablet etc.

  • SMS_SEAMLESS - sendSMS will not show a UI and will just send in the background

  • SMS_INTERACTIVE - sendSMS will show an SMS sending UI

  • SMS_BOTH - sendSMS can support both seamless and interactive mode, this currently only works on Android

The sendSMS can accept an interactive argument: sendSMS(String phoneNumber, String message, boolean interactive)

The last argument will be ignored unless SMS_BOTH is returned from getSMSSupport at which point you would be able to choose one way or the other. The default behavior (when not using that flag) is the background sending which is the current behavior on Android.

A typical use of this API would be something like this:

switch(Display.getInstance().getSMSSupport()) {
    case Display.SMS_NOT_SUPPORTED:
        return;
    case Display.SMS_SEAMLESS:
        showUIDialogToEditMessageData();
        Display.getInstance().sendSMS(phone, data);
        return;
    default:
        Display.getInstance().sendSMS(phone, data);
        return;
}

Dialing

Dialog the phone is pretty trivial, this should open the dialer UI without physically dialing the phone as that is discouraged by device vendors.

You can dial the phone by using:

Display.getInstance().dial("+999999999");

E-Mail

You can send an email via the platforms native email client with code such as this:

Message m = new Message("Body of message");
Display.getInstance().sendMessage(new String[] {"[email protected]"}, "Subject of message", m);

You can add one attachment by using setAttachment and setAttachmentMimeType.

You need to use files from FileSystemStorage and NOT Storage files!

You can add more than one attachment by putting them directly into the attachment map e.g.:

Message m = new Message("Body of message");
m.getAttachments().put(textAttachmentUri, "text/plain");
m.getAttachments().put(imageAttachmentUri, "image/png");
Display.getInstance().sendMessage(new String[] {"[email protected]"}, "Subject of message", m);
Some features such as attachments etc. don’t work correctly in the simulator but should work on iOS/Android

The email messaging API has an additional ability within the Message class. The sendMessageViaCloud method allows you to use the Codename One cloud to send an email without end user interaction. This feature is available to pro users only since it makes use of the Codename One cloud:

Message m = new Message("<html><body>Check out <a href=\"https://www.codenameone.com/\">Codename One</a></body></html>");
m.setMimeType(Message.MIME_HTML);

// notice that we provide a plain text alternative as well in the send method
boolean success = m.sendMessageViaCloudSync("Codename One", "[email protected]", "Name Of User", "Message Subject",
                            "Check out Codename One at https://www.codenameone.com/");

Contacts API

The contacts API provides us with the means to query the phone’s address book, delete elements from it and create new entries into it. To get the platform specific list of contacts you can use String[] contacts = ContactsManager.getAllContacts();

Notice that on some platforms this will prompt the user for permissions and the user might choose not to grant that permission. To detect whether this is the case you can invoke isContactsPermissionGranted() after invoking getAllContacts(). This can help you adapt your error message to the user.

Once you have a Contact you can use the getContactById method, however the default method is a bit slow if you want to pull a large batch of contacts. The solution for this is to only extract the data that you need via

getContactById(String id, boolean includesFullName,
            boolean includesPicture, boolean includesNumbers, boolean includesEmail,
            boolean includeAddress)

Here you can specify true only for the attributes that actually matter to you.

Another capability of the contacts API is the ability to extract all of the contacts very quickly. This isn’t supported on all platforms but platforms such as Android can really get a boost from this API as extracting the contacts one by one is remarkably slow on Android.

You can check if a platform supports the extraction of all the contacts quickly thru ContactsManager.isGetAllContactsFast().

When retrieving all the contacts, notice that you should probably not retrieve all the data and should set some fields to false to perform a more efficient query

You can then extract all the contacts using code that looks a bit like this, notice that we use a thread so the UI won’t be blocked!

Form hi = new Form("Contacts", new BoxLayout(BoxLayout.Y_AXIS));
hi.add(new InfiniteProgress());
Display.getInstance().scheduleBackgroundTask(() -> {
    Contact[] contacts = ContactsManager.getAllContacts(true, true, false, true, false, false);
    Display.getInstance().callSerially(() -> {
        hi.removeAll();
        for(Contact c : contacts) {
            MultiButton mb = new MultiButton(c.getDisplayName());
            mb.setTextLine2(c.getPrimaryPhoneNumber());
            hi.add(mb);
            mb.putClientProperty("id", c.getId());
        }
        hi.getContentPane().animateLayout(150);
    });
});
hi.show();
List of contacts
Figure 1. List of contacts

Notice that we didn’t fetch the image of the contact as the performance of loading these images might be prohibitive. We can enhance the code above to include images by using slightly more complex code such as this:

The scheduleBackgroundTask method is similar to new Thread() in some regards. It places elements in a queue instead of opening too many threads so it can be good for non-urgent tasks
Form hi = new Form("Contacts", new BoxLayout(BoxLayout.Y_AXIS));
hi.add(new InfiniteProgress());
int size = Display.getInstance().convertToPixels(5, true);
FontImage fi = FontImage.createFixed("" + FontImage.MATERIAL_PERSON, FontImage.getMaterialDesignFont(), 0xff, size, size);

Display.getInstance().scheduleBackgroundTask(() -> {
    Contact[] contacts = ContactsManager.getContacts(true, true, false, true, false, false);
    Display.getInstance().callSerially(() -> {
        hi.removeAll();
        for(Contact c : contacts) {
            MultiButton mb = new MultiButton(c.getDisplayName());
            mb.setIcon(fi);
            mb.setTextLine2(c.getPrimaryPhoneNumber());
            hi.add(mb);
            mb.putClientProperty("id", c.getId());
            Display.getInstance().scheduleBackgroundTask(() -> {
                Contact cc = ContactsManager.getContactById(c.getId(), false, true, false, false, false);
                Display.getInstance().callSerially(() -> {
                    Image photo = cc.getPhoto();
                    if(photo != null) {
                        mb.setIcon(photo.fill(size, size));
                        mb.revalidate();
                    }
                });
            });
        }
        hi.getContentPane().animateLayout(150);
    });
});
Contacts with the default photos on the simulator
Figure 2. Contacts with the default photos on the simulator, on device these will use actual user photos when available
Notice that the code above uses callSerially & scheduleBackgroundTask in a liberal nested way. This is important to avoid an EDT violation

You can use createContact(String firstName, String familyName, String officePhone, String homePhone, String cellPhone, String email) to add a new contact and deleteContact(String id) to delete a contact.

Localization & Internationalization (L10N & I18N)

Localization (l10n) means adapting to a locale which is more than just translating to a specific language but also to a specific language within environment e.g. en_US != en_UK. Internationalization (i18n) is the process of creating one application that adapts to all locales and regional requirements.

Codename One supports automatic localization and seamless internationalization of an application using the Codename One design tool.

Although localization is performed in the design tool most features apply to hand coded applications as well. The only exception is the tool that automatically extracts localizable strings from the GUI.
Localization tool in the Designer
Figure 3. Localization tool in the Designer

To translate an application you need to use the localization section of the Codename One Designer. This section features a handy tool to extract localization called Sync With UI, it’s a great tool to get you started assuming you used the old GUI builder.

Some fields on some components (e.g. Commands) are not added when using "Sync With UI" button. But you can add them manually on the localization bundle and they will be automatically localized. You can just use the Property Key used in the localization bundle in the Command name of the form.

You can add additional languages by pressing the Add Locale button.

This generates “bundles” in the resource file which are really just key/value pairs mapping a string in one language to another language. You can install the bundle using code like this:

UIManager.getInstance().setBundle(res.getL10N("l10n", local));

The device language (as an ISO 639 two letter code) could be retrieved with this:

String local = L10NManager.getInstance().getLanguage();

Once installed a resource bundle takes over the UI and every string set to a label (and label like components) will be automatically localized based on the bundle. You can also use the localize method of UIManager to perform localization on your own:

UIManager.getInstance().localize( "KeyInBundle", "DefaultValue");

The list of available languages in the resource bundle could be retrieved like this. Notice that this a list that was set by you and doesn’t need to confirm to the ISO language code standards:

Resources res = fetchResourceFile();
Enumeration locales = res.listL10NLocales( "l10n" );

An exception for localization is the TextField/TextArea components both of which contain user data, in those cases the text will not be localized to avoid accidental localization of user input.

You can preview localization in the theme mode within the Codename One designer by selecting Advanced, picking your locale then clicking the theme again.

You can export and import resource bundles as standard Java properties files, CSV and XML. The formats are pretty standard for most localization shops, the XML format Codename One supports is the one used by Android’s string bundles which means most localization specialists should easily localize it

The resource bundle is just a map between keys and values e.g. the code below displays "This Label is localized" on the Label with the hardcoded resource bundle. It would work the same with a resource bundle loaded from a resource file:

Form hi = new Form("L10N", new BoxLayout(BoxLayout.Y_AXIS));
HashMap<String, String> resourceBudle = new HashMap<String, String>();
resourceBudle.put("Localize", "This Label is localized");
UIManager.getInstance().setBundle(resourceBudle);
hi.add(new Label("Localize"));
hi.show();
Localized label
Figure 4. Localized label

Localization Manager

The L10NManager class includes a multitude of features useful for common localization tasks.

It allows formatting numbers/dates & time based on platform locale. It also provides a great deal of the information you need such as the language/locale information you need to pick the proper resource bundle.

Form hi = new Form("L10N", new TableLayout(16, 2));
L10NManager l10n = L10NManager.getInstance();
hi.add("format(double)").add(l10n.format(11.11)).
    add("format(int)").add(l10n.format(33)).
    add("formatCurrency").add(l10n.formatCurrency(53.267)).
    add("formatDateLongStyle").add(l10n.formatDateLongStyle(new Date())).
    add("formatDateShortStyle").add(l10n.formatDateShortStyle(new Date())).
    add("formatDateTime").add(l10n.formatDateTime(new Date())).
    add("formatDateTimeMedium").add(l10n.formatDateTimeMedium(new Date())).
    add("formatDateTimeShort").add(l10n.formatDateTimeShort(new Date())).
    add("getCurrencySymbol").add(l10n.getCurrencySymbol()).
    add("getLanguage").add(l10n.getLanguage()).
    add("getLocale").add(l10n.getLocale()).
    add("isRTLLocale").add("" + l10n.isRTLLocale()).
    add("parseCurrency").add(l10n.formatCurrency(l10n.parseCurrency("33.77$"))).
    add("parseDouble").add(l10n.format(l10n.parseDouble("34.35"))).
    add("parseInt").add(l10n.format(l10n.parseInt("56"))).
    add("parseLong").add("" + l10n.parseLong("4444444"));
hi.show();
Localization formatting/parsing and information
Figure 5. Localization formatting/parsing and information

RTL/Bidi

RTL stands for right to left, in the world of internationalization it refers to languages that are written from right to left (Arabic, Hebrew, Syriac, Thaana).

Most western languages are written from left to right (LTR), however some languages are written from right to left (RTL) speakers of these languages expect the UI to flow in the opposite direction otherwise it seems weird just like reading this word would be to most English speakers: "drieW".

The problem posed by RTL languages is known as BiDi (Bi-directional) and not as RTL since the "true" problem isn’t the reversal of the writing/UI but rather the mixing of RTL and LTR together. E.g. numbers are always written from left to right (just like in English) so in an RTL language the direction is from right to left and once we reach a number or English text embedded in the middle of the sentence (such as a name) the direction switches for a duration and is later restored.

The main issue in the Codename One world is in the layouts, which need to reverse on the fly. Codename One supports this via an RTL flag on all components that is derived from the global RTL flag in UIManager.

Resource bundles can also include special case constant @rtl, which indicates if a language is written from right to left. This allows everything to automatically reverse.

When in RTL mode the UI will be the exact mirror so WEST will become EAST, RIGHT will become LEFT and this would be true for paddings/margins as well.

If you have a special case where you don’t want this behavior you will need to wrap it with an isRTL check. You can also use setRTL on a per Component basis to disable RTL behavior for a specific Component.

Most UI API’s have special cases for BiDi instead of applying it globally e.g. AWT introduced constants such as LEADING instead of making WEST mean the opposite direction. We think that was a mistake since the cases where you wouldn’t want the behavior of automatic reversal are quite rare.

Codename One’s support for bidi includes the following components:

  • Bidi algorithm - allows converting between logical to visual representation for rendering

  • Global RTL flag - default flag for the entire application indicating the UI should flow from right to left

  • Individual RTL flag - flag indicating that the specific component/container should be presented as an RTL/LTR component (e.g. for displaying English elements within a RTL UI).

  • RTL text field input

Most of Codename One’s RTL support is under the hood, the LookAndFeel global RTL flag can be enabled using:

UIManager.getInstance().getLookAndFeel().setRTL(true);

Once RTL is activated all positions in Codename One become reversed and the UI becomes a mirror of itself. E.g. Adding a Toolbar command to the left will actually make it appear on the right. Padding on the left becomes padding on the right. The scroll moves to the left etc.

This applies to the layout managers (except for group layout) and most components. Bidi is mostly seamless in Codename One but a developer still needs to be aware that his UI might be mirrored for these cases.

Location - GPS

The Location API allows us to track changes in device location or the current user position.

The Simulator includes a Location Simulation tool that you can launch to determine the current position of the simulator and debug location events

The most basic usage for the API allows us to just fetch a device Location, notice that this API is blocking and can take a while to return:

Location position = LocationManager.getLocationManager().getCurrentLocationSync();
In order for location to work on iOS you MUST define the build hint ios.locationUsageDescription and describe why your application needs access to location. Otherwise you won’t get location updates!

The getCurrentLocationSync() method is very good for cases where you only need to fetch a current location once and not repeatedly query location. It activates the GPS then turns it off to avoid excessive battery usage. However, if an application needs to track motion or position over time it should use the location listener API to track location as such:

Notice that there is a method called getCurrentLocation() which will return the current state immediately and might not be accurate for some cases.
public MyListener implements LocationListener {
    public void locationUpdated(Location location) {
        // update UI etc.
    }

    public void providerStateChanged(int newState) {
        // handle status changes/errors appropriately
    }
}
LocationManager.getLocationManager().setLocationListener(new MyListener());
On Android location maps to low level API’s if you disable the usage of Google Play Services. By default location should perform well if you leave the Google Play Services on

Location In The Background - Geofencing

Polling location is generally expensive and requires a special permission on iOS. Its also implemented rather differently both in iOS and Android. Both platforms place restrictions on the location API usage in the background.

Because of the nature of background location the API is non-trivial. It starts with the venerable LocationManager but instead of using the standard API you need to use setBackgroundLocationListener.

Instead of passing a LocationListener instance you need to pass a Class object instance. This is important because background location might be invoked when the app isn’t running and an object would need to be allocated.

Notice that you should NOT perform long operations in the background listener callback. IOS wake-up time is limited to approximately 10 seconds and the app could get killed if it exceeds that time slice.

Notice that the listener can also send events when the app is in the foreground, therefore it is recommended to check the app state before deciding how to process this event. You can use Display.isMinimized() to determine if the app is currently running or in the background.

When implementing this make sure that:

  • The class passed to the API is a public class in the global scope. Not an inner class or anything like that!

  • The class has a public no-argument constructor

  • You need to pass it as a class literal e.g. MyClassName.class. Don’t use Class.forName("my.package.MyClassName")!
    Class names are problematic since device builds are obfuscated, you should only use literals which the obfuscator detects and handles correctly.

The following code demonstrates usage of the GeoFence API:

Geofence gf = new Geofence("test", loc, 100, 100000);

LocationManager.getLocationManager()
        .addGeoFencing(GeofenceListenerImpl.class, gf);
public class GeofenceListenerImpl implements GeofenceListener {
    @Override
    public void onExit(String id) {
    }

    @Override
    public void onEntered(String id) {
        if(Display.getInstance().isMinimized()) {
            Display.getInstance().callSerially(() -> {
                Dialog.show("Welcome", "Thanks for arriving", "OK", null);
            });
        } else {
            LocalNotification ln = new LocalNotification();
            ln.setAlertTitle("Welcome");
            ln.setAlertBody("Thanks for arriving!");
            Display.getInstance().scheduleLocalNotification(ln, 10, false);
        }
    }
}

Background Music Playback

Codename One supports playing music in the background (e.g. when the app is minimized) which is quite useful for developers building a music player style application.

This support isn’t totally portable since the Android and iOS approaches for background music playback differ a great deal. To get this to work on Android you need to use the API: MediaManager.createBackgroundMedia().

You should use that API when you want to create a media stream that will work even when your app is minimized.

For iOS you will need to use a special build hint: ios.background_modes=music.

Which should allow background playback of music on iOS and would work with the createBackgroundMedia() method.

Capture - Photos, Video, Audio

The capture API allows us to use the camera to capture photographs or the microphone to capture audio. It even includes an API for video capture.
The API itself couldn’t be simpler:

String filePath = Capture.capturePhoto();

Just captures and returns a path to a photo you can either open it using the Image class or save it somewhere.

The returned file is a temporary file, you shouldn’t store a reference to it and instead copy it locally or work with the Image object

E.g. you can copy the Image to Storage using:

String filePath = Capture.capturePhoto();
if(filePath != null) {
    Util.copy(FileSystemStorage.getInstance().openInputStream(filePath), Storage.getInstance().createOutputStream(myImageFileName));
}
When running on the simulator the Capture API opens a file chooser API instead of physically capturing the data. This makes debugging device or situation specific issues simpler

We can capture an image from the camera using an API like this:

Form hi = new Form("Capture", new BorderLayout());
hi.setToolbar(new Toolbar());
Style s = UIManager.getInstance().getComponentStyle("Title");
FontImage icon = FontImage.createMaterial(FontImage.MATERIAL_CAMERA, s);

ImageViewer iv = new ImageViewer(icon);

hi.getToolbar().addCommandToRightBar("", icon, (ev) -> {
    String filePath = Capture.capturePhoto();
    if(filePath != null) {
        try {
            DefaultListModel<Image> m = (DefaultListModel<Image>)iv.getImageList();
            Image img = Image.createImage(filePath);
            if(m == null) {
                m = new DefaultListModel<>(img);
                iv.setImageList(m);
                iv.setImage(img);
            } else {
                m.addItem(img);
            }
            m.setSelectedIndex(m.getSize() - 1);
        } catch(IOException err) {
            Log.e(err);
        }
    }
});

hi.add(BorderLayout.CENTER, iv);
hi.show();
Captured photos previewed in the ImageViewer
Figure 6. Captured photos previewed in the ImageViewer

We demonstrate video capture in the MediaManager section.

The sample below captures audio recordings and copies them locally under unique names. It also demonstrates the storage and organization of captured audio:

Form hi = new Form("Capture", BoxLayout.y());
hi.setToolbar(new Toolbar());
Style s = UIManager.getInstance().getComponentStyle("Title");
FontImage icon = FontImage.createMaterial(FontImage.MATERIAL_MIC, s);

FileSystemStorage fs = FileSystemStorage.getInstance();
String recordingsDir = fs.getAppHomePath() + "recordings/";
fs.mkdir(recordingsDir);
try {
    for(String file : fs.listFiles(recordingsDir)) {
        MultiButton mb = new MultiButton(file.substring(file.lastIndexOf("/") + 1));
        mb.addActionListener((e) -> {
            try {
                Media m = MediaManager.createMedia(recordingsDir + file, false);
                m.play();
            } catch(IOException err) {
                Log.e(err);
            }
        });
        hi.add(mb);
    }

    hi.getToolbar().addCommandToRightBar("", icon, (ev) -> {
        try {
            String file = Capture.captureAudio();
            if(file != null) {
                SimpleDateFormat sd = new SimpleDateFormat("yyyy-MMM-dd-kk-mm");
                String fileName =sd.format(new Date());
                String filePath = recordingsDir + fileName;
                Util.copy(fs.openInputStream(file), fs.openOutputStream(filePath));
                MultiButton mb = new MultiButton(fileName);
                mb.addActionListener((e) -> {
                    try {
                        Media m = MediaManager.createMedia(filePath, false);
                        m.play();
                    } catch(IOException err) {
                        Log.e(err);
                    }
                });
                hi.add(mb);
                hi.revalidate();
            }
        } catch(IOException err) {
            Log.e(err);
        }
    });
} catch(IOException err) {
    Log.e(err);
}
hi.show();
Captured recordings in the demo
Figure 7. Captured recordings in the demo

Asynchronous API

The Capture API also includes a callback based API that uses the ActionListener interface to implement capture. E.g. we can adapt the previous sample to use this API as such:

hi.getToolbar().addCommandToRightBar("", icon, (ev) -> {
    Capture.capturePhoto((e) -> {
        if(e != null && e.getSource() != null) {
            try {
                DefaultListModel<Image> m = (DefaultListModel<Image>)iv.getImageList();
                Image img = Image.createImage((String)e.getSource());
                if(m == null) {
                    m = new DefaultListModel<>(img);
                    iv.setImageList(m);
                    iv.setImage(img);
                } else {
                    m.addItem(img);
                }
                m.setSelectedIndex(m.getSize() - 1);
            } catch(IOException err) {
                Log.e(err);
            }
        }
    });
});

The gallery API allows picking an image and/or video from the cameras gallery (camera roll).

Like the Capture API the image returned is a temporary image that should be copied locally, this is due to device restrictions that don’t allow direct modifications of the gallery

We can adapt the Capture sample above to use the gallery as such:

Form hi = new Form("Capture", new BorderLayout());
hi.setToolbar(new Toolbar());
Style s = UIManager.getInstance().getComponentStyle("Title");
FontImage icon = FontImage.createMaterial(FontImage.MATERIAL_CAMERA, s);

ImageViewer iv = new ImageViewer(icon);

hi.getToolbar().addCommandToRightBar("", icon, (ev) -> {
    Display.getInstance().openGallery((e) -> {
        if(e != null && e.getSource() != null) {
            try {
                DefaultListModel<Image> m = (DefaultListModel<Image>)iv.getImageList();
                Image img = Image.createImage((String)e.getSource());
                if(m == null) {
                    m = new DefaultListModel<>(img);
                    iv.setImageList(m);
                    iv.setImage(img);
                } else {
                    m.addItem(img);
                }
                m.setSelectedIndex(m.getSize() - 1);
            } catch(IOException err) {
                Log.e(err);
            }
        }
    }, Display.GALLERY_IMAGE);
});

hi.add(BorderLayout.CENTER, iv);
There is no need for a screenshot as it will look identical to the capture image screenshot above

The last value is the type of content picked which can be one of: Display.GALLERY_ALL, Display.GALLERY_VIDEO or Display.GALLERY_IMAGE.

Analytics Integration

One of the features in Codename One is builtin support for analytic instrumentation. Currently Codename One has builtin support for Google Analytics, which provides reasonable enough statistics of application usage.

Analytics is pretty seamless for the old GUI builder since navigation occurs via the Codename One API and can be logged without developer interaction. However, to begin the instrumentation one needs to add the line:

AnalyticsService.setAppsMode(true);
AnalyticsService.init(agent, domain);

To get the value for the agent value just create a Google Analytics account and add a domain, then copy and paste the string that looks something like UA-99999999-8 from the console to the agent string. Once this is in place you should start receiving statistic events for the application.

If your application is not a GUI builder application or you would like to send more detailed data you can use the Analytics.visit() method to indicate that you are entering a specific page.

Application Level Analytics

In 2013 Google introduced an improved application level analytics API that is specifically built for mobile apps. However, it requires a slightly different API usage. You can activate this specific mode by invoking setAppsMode(true).

When using this mode you can also report errors and crashes to the Google analytics server using the sendCrashReport(Throwable, String message, boolean fatal) method.

We generally recommend using this mode and setting up an apps analytics account as the results are more refined.

Overriding The Analytics Implementation

The Analytics API can also be enhanced to support any other form of analytics solution of your own choosing by deriving the AnalyticsService class.

This allows you to integrate with any 3rd party via native or otherwise by overriding methods in the AnalyticsService class then invoking:

AnalyticsService.init(new MyAnalyticsServiceSubclass());

Notice that this removes the need to invoke the other init method or setAppsMode(boolean).

Native Facebook Support

Check out the ShareButton section it might be enough for most of your needs.

Codename One supports Facebooks Oauth2 login and Facebooks single sign on for iOS and Android.

Getting Started - Web Setup

To get started first you will need to create a facebook app on the Facebook developer portal at https://developers.facebook.com/apps/

Create New App
Figure 8. Create New App

You need to repeat the process for web, Android & iOS (web is used by the simulator):

Pick Platform
Figure 9. Pick Platform

For the first platform you need to enter the app name:

Pick Name
Figure 10. Pick app name

And provide some basic details:

Details
Figure 11. Basic details for the app

For iOS we need the bundle ID which is the exact same thing we used in the Google+ login to identify the iOS app its effectively your package name:

Details
Figure 12. iOS specific basic details

You should end up with something that looks like this:

Details
Figure 13. Finished Facebook app

The Android process is pretty similar but in this case we need the activity name too.

The activity name should match the main class name followed by the word Stub (uppercase s). E.g. for the main class SociallChat we would use SocialChatStub as the activity name
Details
Figure 14. Android Activity definition

To build the native Android app we must make sure that we setup the keystore correctly for our application. If you don’t have an Android certificate you can use the visual wizard (in the Android section in the project preferences the button labeled Generate) or use the command line:

keytool -genkey -keystore Keystore.ks -alias [alias_name] -keyalg RSA -keysize 2048 -validity 15000 -dname "CN=[full name], OU=[ou], O=[comp], L=[City], S=[State], C=[Country Code]" -storepass [password] -keypass [password]
You can reuse the certificate in all your apps, some developers like having a different certificate for every app. This is like having one master key for all your doors, or a huge keyring filled with keys.

With the certificate we need an SHA1 key to further authenticate us to Facebook and we do this using the keytool command line on Linux/Mac:

keytool -exportcert -alias (your_keystore_alias) -keystore (path_to_your_keystore) | openssl sha1 -binary | openssl base64

And on Windows:

keytool -exportcert -alias androiddebugkey -keystore %HOMEPATH%\.android\debug.keystore | openssl sha1 -binary | openssl base64

You can read more about it on the Facebook guide here.

Hash
Figure 15. Hash generation process, notice the command lines are listed as part of the web wizard

Lastly you need to publish the Facebook app by flipping the switch in the apps "Status & Review" page as such:

Enable The App
Figure 16. Without flipping the switch the app won’t "appear"

IDE Setup

We now need to set some important build hints in the project so it will work correctly. To set the build hints just right click the project select project properties and in the Codename One section pick the second tab. Add this entry into the table:

facebook.appId=...

The app ID will be visible in your Facebook app page in the top left position.

The Code

To bind your mobile app into the Facebook app you can use the following code:

Login fb = FacebookConnect.getInstance();

fb.setClientId("9999999");
fb.setRedirectURI("http://www.youruri.com/");
fb.setClientSecret("-------");

// Sets a LoginCallback listener
fb.setCallback(new LoginCallback() {
    public void loginSuccessful() {
        // we can now start fetching stuff from Facebook!
    }

    public void loginFailed(String errorMessage) {}
});

// trigger the login if not already logged in
if(!fb.isUserLoggedIn()){
    fb.doLogin();
} else {
    // get the token and now you can query the Facebook API
    String token = fb.getAccessToken().getToken();
    // ...
}
All of these values are from the web version of the app!
They are only used in the simulator and on "unsupported" platforms as a fallback. Android and iOS will use the native login

Facebook Publish Permissions

In order to post something to Facebook you need to request a write permission, you can only do write operations within the callback which is invoked when the user approves the permission.

You can prompt the user for publish permissions by using this code on a logged in FacebookConnect:

FacebookConnect.getInstance()askPublishPermissions(new LoginCallback() {
    public void loginSuccessful() {
         // do something...
    }
    public void loginFailed(String errorMessage) {
        // show error or just ignore
    }
});
Notice that this won’t always prompt the user, but its required to verify that your token is valid for writing.

Google Login

We can add login to Google’s social account by going to the Google developer console: https://console.developers.google.com/

Create a new app by pressing the create button:

Create New Project
Figure 17. Google developer console

Just enter the name of the app e.g. for this case its SocialChat and press "Create":

Create New Project
Figure 18. Application name

In the new project page we should navigate deeper into the auth API’s for Google+:

New Project Page
Figure 19. API catalog

In that project you should click the Google+ API in the "Social" section:

Google+ API Section
Figure 20. Google+ section

In the credentials section create a new client id, first we need to create one for a web application.

This will be used by the simulator so we can debug the application on the desktop. It will also be used by ports for JavaScript, Desktop and effectively everything other than iOS & Android:

Google+ API Section
Figure 21. Create web application binding

The consent screen is used to prompt users for permissions, you should normally fill it up properly for a "real world" application but in this case we left it mostly empty for simplicities sake:

Concent screen
Figure 22. This data will be shown to users when they want to understand who has access to their account
Web App
Figure 23. OAuth details, the callback URI should generally point to your website

We now need to add Android/iOS native app bindings in much the same way as we did the web app.

To build a native Android app make sure you setup the keystore correctly for your application. Check out the Facebook authentication section above for a similar discussion.

Now that you have a certificate you need two values for your app, the first is the package name which must match the package name of your main class (the one with the start, stop methods). It should be listed in the Codename One properties section.

Make sure to use a name that is 100% unique to you and using your own domain, don’t use the com.codename1 prefix or anything such as that…​

You also need the SHA1 value for your certificate, this is explained by Google here: https://developers.google.com/+/mobile/android/getting-started

Effectively you need to run this command line:

$ keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore -list -v

This will prompt for a password then print several lines of data one of which should start with SHA1 that is the value that you will need for the Android app.

Android App
Figure 24. Paste the SHA1 value here

The iOS app needs the same package name as the bundle id. It also needs the appstore id which you can get from itunes connect, it should appear as the prefix for your apps provisioning profile. If your app is correctly configured e.g. by using the Codename One certificate wizard, then you should see it in the project properties under the Codename One→iOS section.

iOS App
Figure 25. iOS App settings

After everything is completed you should see something similar to this:

End Result
Figure 26. The final result on the web setup

Project Configuration

We now need to set some important build hints in the project so it will work correctly. To set the build hints just right click the project select project properties and in the Codename One section pick the second tab. Add these entries into the table:

ios.gplus.clientId=your ios client ID
Build Hints

The Code

The final login code is very similar to the Facebook login code

Login gc = GoogleConnect.getInstance();
gc.setClientId("*****************.apps.googleusercontent.com");
gc.setRedirectURI("https://yourURL.com/");
gc.setClientSecret("-------------------");

// Sets a LoginCallback listener
gc.setCallback(new LoginCallback() {
    public void loginSuccessful() {
        // we can now start fetching stuff from Google+!
    }

    public void loginFailed(String errorMessage) {}
});

// trigger the login if not already logged in
if(!gc.isUserLoggedIn()){
    gc.doLogin();
} else {
    // get the token and now you can query the Google API
    String token = gc.getAccessToken().getToken();
    // ...
}
The clientid/secret etc. are only relevant for the simulator and should be taken from the web app. The native login automatically connects to the right app.

Lead Component

Codename One has two basic ways to create new components:

  1. Subclass a Component override paint, implement event callbacks etc.

  2. Compose multiple components into a new component, usually by subclassing a Container.

Components such as Tabs subclass Container which make a lot of sense for that component since it is physically a Container.

However, components like MultiButton, SpanButton & SpanLabel don’t necessarily seem like the right candidate for compositing but they are all Container subclasses.

Using a Container provides us a lot of flexibility in terms of layout & functionality for a specific component. MultiButton is a great example of that. It’s a Container internally that is composed of 5 labels and a Button.

Codename One makes the MultiButton "feel" like a single button thru the use of setLeadComponent(Component) which turns the button into the "leader" of the component.

When a Container hierarchy is placed under a leader all events within the hierarchy are sent to the leader, so if a label within the lead component receives a pointer pressed event this event will really be sent to the leader.

E.g. in the case of the MultiButton the internal button will receive that event and send the action performed event, change the state etc.

This creates some potential issues for instance in MultiButton:

myMultiButton.addActionListener((e) -> {
    if(e.getComponent() == myMultiButton) {
        // this won't occur since the source component is really a button!
    }
    if(e.getActualComponent() == myMultiButton) {
        // this will happen...
    }
});

The leader also determines the style state, so all the elements being lead are in the same state. E.g. if the the button is pressed all elements will display their pressed states, notice that they will do so with their own styles but they will each pick the pressed version of that style so a Label UIID within a lead component in the pressed state would return the Pressed state for a Label not for the Button.

This is very convenient when you need to construct more elaborate UI’s and the cool thing about it is that you can do this entirely in the designer which allows assembling containers and defining the lead component inside the hierarchy.

E.g. the SpanButton class is very similar to this code:

public class SpanButton extends Container {
    private Button actualButton;
    private TextArea text;

    public SpanButton(String txt) {
        setUIID("Button");
        setLayout(new BorderLayout());
        text = new TextArea(getUIManager().localize(txt, txt));
        text.setUIID("Button");
        text.setEditable(false);
        text.setFocusable(false);
        actualButton = new Button();
        addComponent(BorderLayout.WEST, actualButton);
        addComponent(BorderLayout.CENTER, text);
        setLeadComponent(actualButton);
    }


    public void setText(String t) {
        text.setText(getUIManager().localize(t, t));
    }

    public void setIcon(Image i) {
        actualButton.setIcon(i);
    }

    public String getText() {
        return text.getText();
    }

    public Image getIcon() {
        return actualButton.getIcon();
    }

    public void addActionListener(ActionListener l) {
        actualButton.addActionListener(l);
    }

    public void removeActionListener(ActionListener l) {
        actualButton.removeActionListener(l);
    }

}

Blocking Lead Behavior

The Component class has two methods that allow us to exclude a component from lead behavior: setBlockLead(boolean) & isBlockLead().

Effectively when you have a Component within the lead hierarchy that you would like to treat differently from the rest you can use this method to exclude it from the lead component behavior while keeping the rest in line…​

This should have no effect if the component isn’t a part of a lead component.

The sample below is based on the Accordion component which uses a lead component internally.

Form f = new Form("Accordion", new BorderLayout());
Accordion accr = new Accordion();
f.getToolbar().addMaterialCommandToRightBar("", FontImage.MATERIAL_ADD, e -> addEntry(accr));
addEntry(accr);
f.add(BorderLayout.CENTER, accr);
f.show();

void addEntry(Accordion accr) {
    TextArea t = new TextArea("New Entry");
    Button delete = new Button();
    FontImage.setMaterialIcon(delete, FontImage.MATERIAL_DELETE);
    Label title = new Label(t.getText());
    t.addActionListener(ee -> title.setText(t.getText()));
    delete.addActionListener(ee -> {
        accr.removeContent(t);
        accr.animateLayout(200);
    });
    delete.setBlockLead(true);
    delete.setUIID("Label");
    Container header = BorderLayout.center(title).
            add(BorderLayout.EAST, delete);
    accr.addContent(header, t);
    accr.animateLayout(200);
}

This allows us to add/edit entries but it also allows the delete button above to actually work separately. Without a call to setBlockLead(true) the delete button would cat as the rest of the accordion title.

Accordion with delete button entries that work despite the surrounding lead
Figure 27. Accordion with delete button entries that work despite the surrounding lead

Pull To Refresh

Pull to refresh is the common UI paradigm that Twitter popularized where the user can pull down the form/container to receive an update. Adding this to Codename One couldn’t be simpler!

Just invoke addPullToRefresh(Runnable) on a scrollable container (or form) and the runnable method will be invoked when the refresh operation occurs.

Pull to refresh is implicitly implements in the InifiniteContainer
Form hi = new Form("Pull To Refresh", BoxLayout.y());
hi.getContentPane().addPullToRefresh(() -> {
    hi.add("Pulled at " + L10NManager.getInstance().formatDateTimeShort(new Date()));
});
hi.show();
Simple pull to refresh demo
Figure 28. Pull to refresh demo

Running 3rd Party Apps Using Display’s execute

The Display class’s execute method allows us to invoke a URL which is bound to a particular application.

This works rather well assuming the application is installed. E.g. this list contains a set of valid URL’s that can be used on iOS to run common applications and use builtin functionality.

Some URL’s might not be supported if an app isn’t installed, on Android there isn’t much that can be done but iOS has a canOpenURL method for Objective-C.

On iOS you can use the Display.canExecute() method which returns a Boolean instead of a boolean which allows us to support 3 result states:

  1. Boolean.TRUE - the URL can be executed

  2. Boolean.FALSE - the URL isn’t supported or the app is missing

  3. null - we have no idea whether the URL will work on this platform.

The sample below launches a "godfather" search on IMDB only when this is sure to work (only on iOS currently). We can actually try to search in the case of null as well but this sample plays it safe by using the http link which is sure to work:

Boolean can = Display.getInstance().canExecute("imdb:///find?q=godfather");
if(can != null && can) {
    Display.getInstance().execute("imdb:///find?q=godfather");
} else {
    Display.getInstance().execute("http://www.imdb.com");
}