Hacking the Source - Part IV
Now that we have the general drift lets move on to Android. In one aspect Android is easier than iOS as it's based on Java. However, other aspects make it a bit painful
and in most regards harder to work with than iOS.
Android Studio
© Codename One 2017 all rights reserved
We will use Android Studio 3.x to build the project since this is pretty much what Google demands.

The first step in building on Android is mostly about running through the IDE steps. The first step is the new project option
Package Name
© Codename One 2017 all rights reserved
When setting the project details the most important bit is the package name. It should match our main package name!
Target Devices
© Codename One 2017 all rights reserved
I left the default settings for the target platform unchanged, this should work fine
Activity
© Codename One 2017 all rights reserved
I chose the blank activity, I'll create one manually later. We are now presented with a progress indicator and then after a while Android Studio finally loads. Notice that you
might need to resolve issues in the IDE if it connects to older versions of the Android SDK installed in your system.
Java 8
© Codename One 2017 all rights reserved
The first thing we need to do once the IDE has finished loading is configure Java 8 support.

The Codename One build servers translate the built bytecodes so they will work without Java 8 support on Android. We need to do that since we have no access to the
source code in the servers and we want to remain compatible.

However, Java 8 is now supported in Android Studio and this will allow your code to compile unchanged. Notice that the core Codename One code is still Java 5
compatible. 

To do this we need to first pick the project structure option from the menu. We then need to set the source & target compatibility to Java 8
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.codename1.demos.kitchen"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main.java.srcDirs += 'src/main'
main.java.srcDirs += '../../KitchenSink/src'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
build.gradle
The next step is to edit the build.gradle file and add support for our sources.

Notice that there are two build.gradle files in the project, you only need to edit the one for the Android project itself which in my case was marked as (Module: app).

After you make changes to system files Android Studio offers to sync the project again which you should accept.

This is the full code but you should probably only take the lines I highlight in the modifications. Lets scroll down to look at the changes
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main.java.srcDirs += 'src/main'
main.java.srcDirs += '../../KitchenSink/src'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:26.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
compile 'com.google.android.gms:play-services-identity:8.3.0'
compile 'com.google.android.gms:play-services-plus:8.3.0'
compile 'com.google.android.gms:play-services-location:8.3.0'
compile 'com.google.android.gms:play-services-auth:8.3.0'
compile 'com.facebook.android:facebook-android-sdk:4.7.0'
}
build.gradle
I added a source set section that points at the sources of the kitchen sink project, that way they appear in the hierarchy but we don't need to copy them
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main.java.srcDirs += 'src/main'
main.java.srcDirs += '../../KitchenSink/src'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:26.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
compile 'com.google.android.gms:play-services-identity:8.3.0'
compile 'com.google.android.gms:play-services-plus:8.3.0'
compile 'com.google.android.gms:play-services-location:8.3.0'
compile 'com.google.android.gms:play-services-auth:8.3.0'
compile 'com.facebook.android:facebook-android-sdk:4.7.0'
}
build.gradle
I added all of these dependencies to the build.

Notice that we don't actually need all of the dependencies. The build servers delete code that you don't need based on build hints. So if Facebook support isn't
necessary it will delete the FacebookImpl class and won't place the facebook dependency. The same is true for the other dependencies mentioned.
android.enableAapt2=false
gradle.properties
We also need to make a minor change to the `gradle.properties` file by adding this line. This is required for some of the style code later on.
cp -Rf ../cn1/CodenameOne/src/* ../
KitchenSinkAndroid/app/src/main/java/
cp -Rf ../cn1/Ports/Android/src/* ../
KitchenSinkAndroid/app/src/main/java/
rm ../KitchenSinkAndroid/app/src/main/java/*
Copy Sources
The next step is copying the Android implementation sources into the project. Since the Android implementation sources don't include the Codename One sources we
need to start with that.

These commands are executed from the KitchenSink directory so they are relative to that path. 

Notice we copy the Codename One code first and the Android code second so files get overwritten. Also notice we delete all the files in the root of the project source
(not recursively). The root of the Codename One and Android projects include resource files that should be placed in a different location.

Android expects all resources that aren't in the res file to be within an assets directory. We need to create that directory in the hierarchy where the res directory resides.
mkdir ../KitchenSinkAndroid/app/src/main/assets
cp ../cn1/CodenameOne/src/* ../KitchenSinkAndroid/
app/src/main/assets/
cp ../cn1/Ports/Android/src/* ../
KitchenSinkAndroid/app/src/main/assets/
cp src/* ../KitchenSinkAndroid/app/src/main/assets
Copy Assets
This copies the files from the roots of all of these projects. Notice the order of commands sets the priority since a project on the way can override the parent project.

Following these instructions you might be thinking “Why Copy?”.

We included the source of the Kitchen Sink, why not do the same for the implementation?

The main problem is overriden files. Codename One redefines the CodenameOneThread class in the Android implementation and relies on that redefinition taking the
priority. We might do more of that in the future.

This is also important because you might want to delete some files such as Facebook support. So copy makes more sense in this special case.
package com.codename1.demos.kitchen;
import com.codename1.impl.android.AndroidImplementation;
import com.codename1.impl.android.CodenameOneActivity;
import com.codename1.ui.Dialog;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
public class KitchenSinkStub extends CodenameOneActivity implements Runnable {
private static KitchenSink i;
private static boolean firstTime = true;
private Form currentForm;
private static final Object LOCK = new Object();
protected void onResume() {
super.onResume();
AndroidImplementation.startContext(this);
if (i == null) {
i = new KitchenSink();
}
Display.getInstance().callSerially(this);
}
public void run() {
if (firstTime) {
firstTime = false;
i.init(this);
KitchenSinkStub
The next step is the activity class which is Androids lifecycle object. This is a pretty standard class in Android so I'll add the code & step over it like before.

We have a CodenameOneActivity base class which is important for the main activity as we handle some nuanced events there
package com.codename1.demos.kitchen;
import com.codename1.impl.android.AndroidImplementation;
import com.codename1.impl.android.CodenameOneActivity;
import com.codename1.ui.Dialog;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
public class KitchenSinkStub extends CodenameOneActivity implements Runnable {
private static KitchenSink i;
private static boolean firstTime = true;
private Form currentForm;
private static final Object LOCK = new Object();
protected void onResume() {
super.onResume();
AndroidImplementation.startContext(this);
if (i == null) {
i = new KitchenSink();
}
Display.getInstance().callSerially(this);
}
public void run() {
if (firstTime) {
firstTime = false;
i.init(this);
KitchenSinkStub
Most Android apps use onCreate to detect app launch. We use onResume which is more consistent for our needs in terms of suspend/resume behavior
package com.codename1.demos.kitchen;
import com.codename1.impl.android.AndroidImplementation;
import com.codename1.impl.android.CodenameOneActivity;
import com.codename1.ui.Dialog;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
public class KitchenSinkStub extends CodenameOneActivity implements Runnable {
private static KitchenSink i;
private static boolean firstTime = true;
private Form currentForm;
private static final Object LOCK = new Object();
protected void onResume() {
super.onResume();
AndroidImplementation.startContext(this);
if (i == null) {
i = new KitchenSink();
}
Display.getInstance().callSerially(this);
}
public void run() {
if (firstTime) {
firstTime = false;
i.init(this);
KitchenSinkStub
These lines effectively initialize Codename One and start the EDT if it isn't running. We also allocate the KitchenSink main class if it isn't allocated yet
package com.codename1.demos.kitchen;
import com.codename1.impl.android.AndroidImplementation;
import com.codename1.impl.android.CodenameOneActivity;
import com.codename1.ui.Dialog;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
public class KitchenSinkStub extends CodenameOneActivity implements Runnable {
private static KitchenSink i;
private static boolean firstTime = true;
private Form currentForm;
private static final Object LOCK = new Object();
protected void onResume() {
super.onResume();
AndroidImplementation.startContext(this);
if (i == null) {
i = new KitchenSink();
}
Display.getInstance().callSerially(this);
}
public void run() {
if (firstTime) {
firstTime = false;
i.init(this);
KitchenSinkStub
I did the allocation on the Android thread which isn't ideal but the callback should be invoked on the Codename One thread which is why we have this callSerially here
public void run() {
if (firstTime) {
firstTime = false;
i.init(this);
} else {
synchronized (LOCK) {
if (currentForm != null) {
if (currentForm instanceof Dialog) {
((Dialog) currentForm).showModeless();
} else {
currentForm.show();
}
fireIntentResult();
currentForm = null;
setWaitingForResult(false);
return;
}
}
}
i.start();
}
protected void onPause() {
super.onPause();
KitchenSinkStub
Now on the EDT if this is the first time we need to invoke the `init(Object)` method of the main class
public void run() {
if (firstTime) {
firstTime = false;
i.init(this);
} else {
synchronized (LOCK) {
if (currentForm != null) {
if (currentForm instanceof Dialog) {
((Dialog) currentForm).showModeless();
} else {
currentForm.show();
}
fireIntentResult();
currentForm = null;
setWaitingForResult(false);
return;
}
}
}
i.start();
}
protected void onPause() {
super.onPause();
KitchenSinkStub
Otherwise if we are resuming we try to be "smart" about the current Form since Android has a tendency to restart activities for everything
public void run() {
if (firstTime) {
firstTime = false;
i.init(this);
} else {
synchronized (LOCK) {
if (currentForm != null) {
if (currentForm instanceof Dialog) {
((Dialog) currentForm).showModeless();
} else {
currentForm.show();
}
fireIntentResult();
currentForm = null;
setWaitingForResult(false);
return;
}
}
}
i.start();
}
protected void onPause() {
super.onPause();
KitchenSinkStub
start() is invoked with every call to resume to match the lifecycle behavior of Codename One. The same holds true to stop and destroy both of which are pretty trivial
i.start();
}
protected void onPause() {
super.onPause();
synchronized (LOCK) {
currentForm = Display.getInstance().getCurrent();
}
}
protected void onStop() {
super.onStop();
if (isWaitingForResult()) {
return;
}
synchronized (LOCK) {
currentForm = null;
}
Display.getInstance().callSerially(() -> i.stop());
}
protected void onDestroy() {
super.onDestroy();
Display.getInstance().callSerially(() -> {
i.destroy();
Display.deinitialize();
});
}
}
KitchenSinkStub
The rest of the code is just boilerplate and uninteresting. We invoke stop in onStop and invoke destroy in onDestroy()
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.codename1.demos.kitchen">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/CustomTheme" >
<activity android:name=".KitchenSinkStub"
android:configChanges="orientation|keyboardHidden|screenSize"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Manifest
Now that all of that is out of the way we just need to configure the XML files. First we have the manifest file which represents all the activities in the application. The
manifest isn't really important here. I just used the default manifest generated by the IDE and updated the activity entry. Notice that most of the entries within the activity
are essential for the app.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ff000000">
</RelativeLayout>
main.xml
We also need a layout for the main application so under the res/layout directory we need to add the file main.xml. That's a pretty simple layout just to give room for
Codename One itself.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="CustomTheme" parent="android:Theme.Black">
<item name="attr/cn1Style">@style/CN1.EditText.Style</item>
</style>
<attr name="cn1Style" format="reference" />
<style name="CN1.EditText.Style"
parent="@android:style/Widget.EditText">
<item name="android:textCursorDrawable">@null</item>
</style>
</resources>
styles.xml
We need 3 style files to support all this, the first is styles.xml which resides in the directory src/main/res/values. Which just includes a theme and not much else.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="CustomTheme"
parent="@android:style/Theme.Holo.Light">
<item name="attr/cn1Style">@style/CN1.EditText.Style</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowTitleSize">0dp</item>
</style>
<style name="CN1.EditText.Style"
parent="@android:style/Widget.EditText">
<item name="android:textCursorDrawable">@null</item>
</style>
</resources>
styles.xml (v11)
The second bares the same name but resides in the directory src/main/res/values-v11. Again this is pretty trivial and hardcoded for all the apps
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="CustomTheme"
parent="@android:style/Theme.Material.Light">
<item name="attr/cn1Style">@style/CN1.EditText.Style</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowTitleSize">0dp</item>
<item name="android:colorPrimary">@color/colorPrimary</item>
<item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="android:colorAccent">@color/colorAccent</item>
</style>
<style name="CN1.EditText.Style"
parent="@android:style/Widget.EditText">
<item name="android:textCursorDrawable">@null</item>
</style>
</resources>
styles.xml (v21)
The final one also has the same name but resides in the directory src/main/res/values-v21. It includes some additional settings for newer versions of Android. 

Now that all this is done you can just press play in the IDE and run on an Android device! There was a bit of boilerplate but I hope it’s clear

Hacking the Codename One Source Code - Part IV - Transcript.pdf

  • 1.
    Hacking the Source- Part IV Now that we have the general drift lets move on to Android. In one aspect Android is easier than iOS as it's based on Java. However, other aspects make it a bit painful and in most regards harder to work with than iOS.
  • 2.
    Android Studio © CodenameOne 2017 all rights reserved We will use Android Studio 3.x to build the project since this is pretty much what Google demands. The first step in building on Android is mostly about running through the IDE steps. The first step is the new project option
  • 3.
    Package Name © CodenameOne 2017 all rights reserved When setting the project details the most important bit is the package name. It should match our main package name!
  • 4.
    Target Devices © CodenameOne 2017 all rights reserved I left the default settings for the target platform unchanged, this should work fine
  • 5.
    Activity © Codename One2017 all rights reserved I chose the blank activity, I'll create one manually later. We are now presented with a progress indicator and then after a while Android Studio finally loads. Notice that you might need to resolve issues in the IDE if it connects to older versions of the Android SDK installed in your system.
  • 6.
    Java 8 © CodenameOne 2017 all rights reserved The first thing we need to do once the IDE has finished loading is configure Java 8 support. The Codename One build servers translate the built bytecodes so they will work without Java 8 support on Android. We need to do that since we have no access to the source code in the servers and we want to remain compatible. However, Java 8 is now supported in Android Studio and this will allow your code to compile unchanged. Notice that the core Codename One code is still Java 5 compatible. To do this we need to first pick the project structure option from the menu. We then need to set the source & target compatibility to Java 8
  • 7.
    apply plugin: 'com.android.application' android{ compileSdkVersion 26 defaultConfig { applicationId "com.codename1.demos.kitchen" minSdkVersion 15 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } sourceSets { main.java.srcDirs += 'src/main' main.java.srcDirs += '../../KitchenSink/src' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } build.gradle The next step is to edit the build.gradle file and add support for our sources. Notice that there are two build.gradle files in the project, you only need to edit the one for the Android project itself which in my case was marked as (Module: app). After you make changes to system files Android Studio offers to sync the project again which you should accept. This is the full code but you should probably only take the lines I highlight in the modifications. Lets scroll down to look at the changes
  • 8.
    minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro' } } sourceSets { main.java.srcDirs += 'src/main' main.java.srcDirs += '../../KitchenSink/src' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.android.support:appcompat-v7:26.1.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' compile 'com.google.android.gms:play-services-identity:8.3.0' compile 'com.google.android.gms:play-services-plus:8.3.0' compile 'com.google.android.gms:play-services-location:8.3.0' compile 'com.google.android.gms:play-services-auth:8.3.0' compile 'com.facebook.android:facebook-android-sdk:4.7.0' } build.gradle I added a source set section that points at the sources of the kitchen sink project, that way they appear in the hierarchy but we don't need to copy them
  • 9.
    minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro' } } sourceSets { main.java.srcDirs += 'src/main' main.java.srcDirs += '../../KitchenSink/src' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.android.support:appcompat-v7:26.1.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' compile 'com.google.android.gms:play-services-identity:8.3.0' compile 'com.google.android.gms:play-services-plus:8.3.0' compile 'com.google.android.gms:play-services-location:8.3.0' compile 'com.google.android.gms:play-services-auth:8.3.0' compile 'com.facebook.android:facebook-android-sdk:4.7.0' } build.gradle I added all of these dependencies to the build. Notice that we don't actually need all of the dependencies. The build servers delete code that you don't need based on build hints. So if Facebook support isn't necessary it will delete the FacebookImpl class and won't place the facebook dependency. The same is true for the other dependencies mentioned.
  • 10.
    android.enableAapt2=false gradle.properties We also needto make a minor change to the `gradle.properties` file by adding this line. This is required for some of the style code later on.
  • 11.
    cp -Rf ../cn1/CodenameOne/src/*../ KitchenSinkAndroid/app/src/main/java/ cp -Rf ../cn1/Ports/Android/src/* ../ KitchenSinkAndroid/app/src/main/java/ rm ../KitchenSinkAndroid/app/src/main/java/* Copy Sources The next step is copying the Android implementation sources into the project. Since the Android implementation sources don't include the Codename One sources we need to start with that. These commands are executed from the KitchenSink directory so they are relative to that path. Notice we copy the Codename One code first and the Android code second so files get overwritten. Also notice we delete all the files in the root of the project source (not recursively). The root of the Codename One and Android projects include resource files that should be placed in a different location. Android expects all resources that aren't in the res file to be within an assets directory. We need to create that directory in the hierarchy where the res directory resides.
  • 12.
    mkdir ../KitchenSinkAndroid/app/src/main/assets cp ../cn1/CodenameOne/src/*../KitchenSinkAndroid/ app/src/main/assets/ cp ../cn1/Ports/Android/src/* ../ KitchenSinkAndroid/app/src/main/assets/ cp src/* ../KitchenSinkAndroid/app/src/main/assets Copy Assets This copies the files from the roots of all of these projects. Notice the order of commands sets the priority since a project on the way can override the parent project. Following these instructions you might be thinking “Why Copy?”. We included the source of the Kitchen Sink, why not do the same for the implementation? The main problem is overriden files. Codename One redefines the CodenameOneThread class in the Android implementation and relies on that redefinition taking the priority. We might do more of that in the future. This is also important because you might want to delete some files such as Facebook support. So copy makes more sense in this special case.
  • 13.
    package com.codename1.demos.kitchen; import com.codename1.impl.android.AndroidImplementation; importcom.codename1.impl.android.CodenameOneActivity; import com.codename1.ui.Dialog; import com.codename1.ui.Display; import com.codename1.ui.Form; public class KitchenSinkStub extends CodenameOneActivity implements Runnable { private static KitchenSink i; private static boolean firstTime = true; private Form currentForm; private static final Object LOCK = new Object(); protected void onResume() { super.onResume(); AndroidImplementation.startContext(this); if (i == null) { i = new KitchenSink(); } Display.getInstance().callSerially(this); } public void run() { if (firstTime) { firstTime = false; i.init(this); KitchenSinkStub The next step is the activity class which is Androids lifecycle object. This is a pretty standard class in Android so I'll add the code & step over it like before. We have a CodenameOneActivity base class which is important for the main activity as we handle some nuanced events there
  • 14.
    package com.codename1.demos.kitchen; import com.codename1.impl.android.AndroidImplementation; importcom.codename1.impl.android.CodenameOneActivity; import com.codename1.ui.Dialog; import com.codename1.ui.Display; import com.codename1.ui.Form; public class KitchenSinkStub extends CodenameOneActivity implements Runnable { private static KitchenSink i; private static boolean firstTime = true; private Form currentForm; private static final Object LOCK = new Object(); protected void onResume() { super.onResume(); AndroidImplementation.startContext(this); if (i == null) { i = new KitchenSink(); } Display.getInstance().callSerially(this); } public void run() { if (firstTime) { firstTime = false; i.init(this); KitchenSinkStub Most Android apps use onCreate to detect app launch. We use onResume which is more consistent for our needs in terms of suspend/resume behavior
  • 15.
    package com.codename1.demos.kitchen; import com.codename1.impl.android.AndroidImplementation; importcom.codename1.impl.android.CodenameOneActivity; import com.codename1.ui.Dialog; import com.codename1.ui.Display; import com.codename1.ui.Form; public class KitchenSinkStub extends CodenameOneActivity implements Runnable { private static KitchenSink i; private static boolean firstTime = true; private Form currentForm; private static final Object LOCK = new Object(); protected void onResume() { super.onResume(); AndroidImplementation.startContext(this); if (i == null) { i = new KitchenSink(); } Display.getInstance().callSerially(this); } public void run() { if (firstTime) { firstTime = false; i.init(this); KitchenSinkStub These lines effectively initialize Codename One and start the EDT if it isn't running. We also allocate the KitchenSink main class if it isn't allocated yet
  • 16.
    package com.codename1.demos.kitchen; import com.codename1.impl.android.AndroidImplementation; importcom.codename1.impl.android.CodenameOneActivity; import com.codename1.ui.Dialog; import com.codename1.ui.Display; import com.codename1.ui.Form; public class KitchenSinkStub extends CodenameOneActivity implements Runnable { private static KitchenSink i; private static boolean firstTime = true; private Form currentForm; private static final Object LOCK = new Object(); protected void onResume() { super.onResume(); AndroidImplementation.startContext(this); if (i == null) { i = new KitchenSink(); } Display.getInstance().callSerially(this); } public void run() { if (firstTime) { firstTime = false; i.init(this); KitchenSinkStub I did the allocation on the Android thread which isn't ideal but the callback should be invoked on the Codename One thread which is why we have this callSerially here
  • 17.
    public void run(){ if (firstTime) { firstTime = false; i.init(this); } else { synchronized (LOCK) { if (currentForm != null) { if (currentForm instanceof Dialog) { ((Dialog) currentForm).showModeless(); } else { currentForm.show(); } fireIntentResult(); currentForm = null; setWaitingForResult(false); return; } } } i.start(); } protected void onPause() { super.onPause(); KitchenSinkStub Now on the EDT if this is the first time we need to invoke the `init(Object)` method of the main class
  • 18.
    public void run(){ if (firstTime) { firstTime = false; i.init(this); } else { synchronized (LOCK) { if (currentForm != null) { if (currentForm instanceof Dialog) { ((Dialog) currentForm).showModeless(); } else { currentForm.show(); } fireIntentResult(); currentForm = null; setWaitingForResult(false); return; } } } i.start(); } protected void onPause() { super.onPause(); KitchenSinkStub Otherwise if we are resuming we try to be "smart" about the current Form since Android has a tendency to restart activities for everything
  • 19.
    public void run(){ if (firstTime) { firstTime = false; i.init(this); } else { synchronized (LOCK) { if (currentForm != null) { if (currentForm instanceof Dialog) { ((Dialog) currentForm).showModeless(); } else { currentForm.show(); } fireIntentResult(); currentForm = null; setWaitingForResult(false); return; } } } i.start(); } protected void onPause() { super.onPause(); KitchenSinkStub start() is invoked with every call to resume to match the lifecycle behavior of Codename One. The same holds true to stop and destroy both of which are pretty trivial
  • 20.
    i.start(); } protected void onPause(){ super.onPause(); synchronized (LOCK) { currentForm = Display.getInstance().getCurrent(); } } protected void onStop() { super.onStop(); if (isWaitingForResult()) { return; } synchronized (LOCK) { currentForm = null; } Display.getInstance().callSerially(() -> i.stop()); } protected void onDestroy() { super.onDestroy(); Display.getInstance().callSerially(() -> { i.destroy(); Display.deinitialize(); }); } } KitchenSinkStub The rest of the code is just boilerplate and uninteresting. We invoke stop in onStop and invoke destroy in onDestroy()
  • 21.
    <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.codename1.demos.kitchen"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/CustomTheme" > <activityandroid:name=".KitchenSinkStub" android:configChanges="orientation|keyboardHidden|screenSize" android:launchMode="singleTop"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> Manifest Now that all of that is out of the way we just need to configure the XML files. First we have the manifest file which represents all the activities in the application. The manifest isn't really important here. I just used the default manifest generated by the IDE and updated the activity entry. Notice that most of the entries within the activity are essential for the app.
  • 22.
    <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#ff000000"> </RelativeLayout> main.xml Wealso need a layout for the main application so under the res/layout directory we need to add the file main.xml. That's a pretty simple layout just to give room for Codename One itself.
  • 23.
    <?xml version="1.0" encoding="utf-8"?> <resources> <stylename="CustomTheme" parent="android:Theme.Black"> <item name="attr/cn1Style">@style/CN1.EditText.Style</item> </style> <attr name="cn1Style" format="reference" /> <style name="CN1.EditText.Style" parent="@android:style/Widget.EditText"> <item name="android:textCursorDrawable">@null</item> </style> </resources> styles.xml We need 3 style files to support all this, the first is styles.xml which resides in the directory src/main/res/values. Which just includes a theme and not much else.
  • 24.
    <?xml version="1.0" encoding="utf-8"?> <resources> <stylename="CustomTheme" parent="@android:style/Theme.Holo.Light"> <item name="attr/cn1Style">@style/CN1.EditText.Style</item> <item name="android:windowActionBar">false</item> <item name="android:windowTitleSize">0dp</item> </style> <style name="CN1.EditText.Style" parent="@android:style/Widget.EditText"> <item name="android:textCursorDrawable">@null</item> </style> </resources> styles.xml (v11) The second bares the same name but resides in the directory src/main/res/values-v11. Again this is pretty trivial and hardcoded for all the apps
  • 25.
    <?xml version="1.0" encoding="utf-8"?> <resources> <stylename="CustomTheme" parent="@android:style/Theme.Material.Light"> <item name="attr/cn1Style">@style/CN1.EditText.Style</item> <item name="android:windowActionBar">false</item> <item name="android:windowTitleSize">0dp</item> <item name="android:colorPrimary">@color/colorPrimary</item> <item name="android:colorPrimaryDark">@color/colorPrimaryDark</item> <item name="android:colorAccent">@color/colorAccent</item> </style> <style name="CN1.EditText.Style" parent="@android:style/Widget.EditText"> <item name="android:textCursorDrawable">@null</item> </style> </resources> styles.xml (v21) The final one also has the same name but resides in the directory src/main/res/values-v21. It includes some additional settings for newer versions of Android. Now that all this is done you can just press play in the IDE and run on an Android device! There was a bit of boilerplate but I hope it’s clear