tag:blogger.com,1999:blog-86500101751989811482024-03-02T02:07:08.967+01:00blog.mobile-j.deMy humble opinions about Java on mobile devices and Java in general and stuff like that.Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.comBlogger31125tag:blogger.com,1999:blog-8650010175198981148.post-75856418669363530812018-05-28T10:18:00.003+02:002018-05-28T10:18:40.120+02:00Video on AndroidBefore I started working on the app <a href="https://play.google.com/store/apps/details?id=de.mobilej.recapp">Device Video Maker</a> I haven't used much of Android's media api. That app let's you capture the screen and create a nice video with the screen capture rendered into a device frame together with custom text.
I was surprised that quite few articles are there on the web. I think the api documentation is pretty good but to be honest I had overseen a lot of details from there and I had to learn things the hard way.
E.g. that CSD thing puzzled me a lot and the errors you will see in your logs when you do something wrong are very confusing and cryptic. That's a bad experience when developing such an app.
One of the best resources (beside the api documentation - you should read that carefully) is <a href="https://bigflake.com/mediacodec/">Big Flake</a> - it helped me a lot. It's not that easy to understand everything since it references mainly CTS code and code that at least looks like CTS code. But it should definitely be your second stop after the api docs.Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com1tag:blogger.com,1999:blog-8650010175198981148.post-1167141611623441282018-03-10T12:37:00.000+01:002018-03-10T12:37:45.674+01:00Trying to generate downloads in an overcrowded categoryWe all know the gold rush in app stores is over.<br/><br/>
I just decided to see if it's still possible to generate some downloads in an overcrowded category.<br/><br/>
I looked for something that is really easy to implement, has much competition and the competitors feature high volume downloads: <a href="https://play.google.com/store/apps/details?id=de.mobilej.fliptext">Crazy-Text</a><br/><br/>
It's really simple - yes. But that's the point.<br/><br/>
I document everything I do in a spreadsheet and I am going to write down and publish the findings.<br/><br/>
There are almost no downloads yet - so it's an experiment. Let's see what will happen to this tiny app.Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com0tag:blogger.com,1999:blog-8650010175198981148.post-66475330412560645172014-11-05T09:11:00.001+01:002014-11-05T09:11:01.973+01:00Android/Gradle: Have debug and release variants for a library<br/>
<script src="https://gist.github.com/bjoernQ/2fe58fc28345cfc5db0a.js"></script>Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com1tag:blogger.com,1999:blog-8650010175198981148.post-49796801768127816132013-10-14T15:23:00.000+02:002013-10-14T15:23:20.033+02:00Script for doing a backup of an Android app and unpacking itRecent Android versions added the ability to create backups via "adb backup". I use this during development if I want to look inside the database files (and other files written to the app's private directories).<br />
<br />
Quite useful. Obviously you shouldn't encrypt the backup :)
<br />
<br />
<br />
<script src="https://gist.github.com/bjoernQ/6975476.js"></script>Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com0tag:blogger.com,1999:blog-8650010175198981148.post-50343286734955892202013-10-14T15:03:00.003+02:002013-10-14T15:03:59.734+02:00Creating a System Overlay (Always on Top over all Apps) in AndroidI don't think this is something I want to see overused by apps but I guess there are some very cool things possible with this.<br />
<br />
I have already seen apps using this concept in a good way and I already have ideas what to do with this (once I get enough spare time).<br />
<br />
The code is based on the solution outlined at <a href="http://stackoverflow.com/questions/4481226/creating-a-system-overlay-always-on-top-button-in-android">http://stackoverflow.com/questions/4481226/creating-a-system-overlay-always-on-top-button-in-android</a> but this one also adds support for moving the view around by dragging it.
<br />
<br />
<br />
<script src="https://gist.github.com/bjoernQ/6975256.js"></script>Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com3tag:blogger.com,1999:blog-8650010175198981148.post-65529369823786871642013-10-14T14:37:00.001+02:002013-10-14T14:37:35.474+02:00A fluent API for ContentResolver#queryOne thing that always felt ugly was using ContentResolver#query. A line like this isn't very readable and changing the query often introduce new bugs:<br />
<div>
<br /></div>
<div>
c = getContentResolver().query(Contacts.CONTENT_URI, new String[]{Contacts._ID,Contacts.DISPLAY_NAME}, "("+Contacts.HAS_PHONE_NUMBER+"=? AND "+Contacts.STARRED+"=?) OR ("+Contacts.HAS_PHONE_NUMBER+"=? AND "+Contacts.STARRED+"<>?)", new String[]{"1","0","1","0"}, Contacts.DISPLAY_NAME);
</div>
<div>
<br /></div>
<div>
So I thought it would be nice if you could write this query like this:<br />
<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span> c = Query.create().select(Contacts._ID,Contacts.DISPLAY_NAME).<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span> where( Query.create().<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span> where(Contacts.HAS_PHONE_NUMBER).eq("1").<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span> and().where(Contacts.STARRED).eq("0")<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span> ).or().<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span> where( Query.create().<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span> where(Contacts.HAS_PHONE_NUMBER).eq("1").<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span> and().where(Contacts.STARRED).ne("0")<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span> ).<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span> orderBy(Contacts.DISPLAY_NAME).<br />
<span class="Apple-tab-span" style="white-space: pre;"> </span> build().execute(getContentResolver(), Contacts.CONTENT_URI);<br />
<div>
<br /></div>
<div>
IMHO this is easier to read, easier to understand and easier to maintain. So I did a quick proof of concept and it turned out to be pretty easy.
<br/>
</div>
<script src="https://gist.github.com/anonymous/6974860.js"></script>
<div>
<br /></div>
<div>
<br /></div>
<br /></div>
Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com0tag:blogger.com,1999:blog-8650010175198981148.post-26262519772002617322013-06-20T14:44:00.004+02:002013-06-20T14:44:53.683+02:00Pull to Reveal Additional OptionsRecently I have seen some screen designs for an Android app where the user can pull a listview to reveal additional options e.g. sort options or something like that. So I started to think about a possible approach for this.<br />
<div>
<br /></div>
<div>
Header views are not what we want so I thought about inheriting from ListView and add the functionality but aber a look at the source I thought there must be an easier solution to this.</div>
<div>
<br /></div>
<div>
So my solution was to create a subclass of LinearLayout which intercepts touch events. There are two children. A container for the additional options at the top and the ListView of cause.</div>
<div>
<br /></div>
<div>
And this turned out to be an easy and nice solution IMHO.</div>
<div>
<br /></div>
<div>
Here is the source of that touch interception LinearLayout:</div>
<div>
<br /></div>
<div>
<br /></div>
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVanIRh5z6spW3hG5LdDUFCXmxx61P_ZcxKLQnC2TsxeshMcJ-iw04TmaGJKok3gV904UgXGfc7nJ7vc8WITuRwCTxpgjaiZrUYRHOWk6p4RON0yLP8hnDUFuZ6KgEoyVtXpF4LkMju7ll/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> package de.mobilej.widget;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.View;
import android.view.animation.OvershootInterpolator;
import android.widget.LinearLayout;
import android.widget.ListView;
import de.mobilej.listboxtest.R;
public class TopDrawer extends LinearLayout {
private boolean headerVisible;
private ListView lv;
private int originalLvBottom;
PointerCoords pointerMoveStart = new PointerCoords();
boolean scrolling = false;
private View v;
public TopDrawer(Context context) {
super(context);
}
public TopDrawer(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TopDrawer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (v == null) {
v = findViewById(R.id.header);
lv = (ListView) findViewById(R.id.mylist);
}
originalLvBottom = lv.getBottom();
if (!headerVisible) {
v.setTranslationY(-1 * v.getHeight());
lv.setTranslationY(-1 * v.getHeight());
lv.setBottom(originalLvBottom + v.getHeight());
}
}
public void colapse() {
headerVisible = false;
lv.setBottom(originalLvBottom + v.getHeight());
float curTrans = v.getTranslationY();
float destTrans = -1 * v.getHeight();
if (curTrans > destTrans) {
v.animate().translationY(destTrans).setDuration(300).setInterpolator(new OvershootInterpolator()).start();
lv.animate().translationY(destTrans).setDuration(300).setInterpolator(new OvershootInterpolator()).start();
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
scrolling = true;
ev.getPointerCoords(0, pointerMoveStart);
} else if (action == MotionEvent.ACTION_MOVE && scrolling) {
PointerCoords currentPos = new PointerCoords();
ev.getPointerCoords(0, currentPos);
if (pointerMoveStart.y - currentPos.y < 0) {
if (lv.getFirstVisiblePosition() == 0) {
View topChild = lv.getChildAt(0);
if (topChild.getY() == 0) {
return true;
}
}
}
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_MOVE && scrolling && !headerVisible) {
PointerCoords currentPos = new PointerCoords();
ev.getPointerCoords(0, currentPos);
if (pointerMoveStart.y - currentPos.y < 0) {
if (lv.getFirstVisiblePosition() == 0) {
View topChild = lv.getChildAt(0);
if (topChild.getY() == 0) {
int hHeader = v.getHeight();
int moved = (int) (currentPos.y - pointerMoveStart.y);
int trans = -hHeader + moved;
if (trans > 0) {
trans = 0;
headerVisible = true;
}
v.setTranslationY(trans);
lv.setTranslationY(trans);
lv.setBottom(originalLvBottom - trans);
return true;
}
}
}
} else if (action == MotionEvent.ACTION_UP) {
scrolling = false;
if (!headerVisible) {
lv.setBottom(originalLvBottom + v.getHeight());
float curTrans = v.getTranslationY();
float destTrans = -1 * v.getHeight();
if (curTrans > destTrans) {
v.animate().translationY(destTrans).setDuration(300).setInterpolator(new OvershootInterpolator())
.start();
lv.animate().translationY(destTrans).setDuration(300).setInterpolator(new OvershootInterpolator())
.start();
}
}
}
return false;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
Bundle b = (Bundle) state;
if (b == null) {
return;
}
super.onRestoreInstanceState(b.getParcelable("SUPER"));
headerVisible = b.getBoolean("visible");
}
@Override
protected Parcelable onSaveInstanceState() {
Bundle b = new Bundle();
b.putParcelable("SUPER", super.onSaveInstanceState());
b.putBoolean("visible", headerVisible);
return b;
}
}
</code></pre>
<br />
It's not fully polished code but just a quick prove of concept. And it's also not possible currently to hide the additional views easily by e.g. overscrolling to the end of the list (in my example I have a button that calls the collape method). But all this can be added easily.<br />
<br />
A possible layout file could look like this<br />
<br />
<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVanIRh5z6spW3hG5LdDUFCXmxx61P_ZcxKLQnC2TsxeshMcJ-iw04TmaGJKok3gV904UgXGfc7nJ7vc8WITuRwCTxpgjaiZrUYRHOWk6p4RON0yLP8hnDUFuZ6KgEoyVtXpF4LkMju7ll/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<de.mobilej.widget.TopDrawer
android:id="@+id/topDrawer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<LinearLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<EditText
android:id="@+id/myEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<ImageButton
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/btn_star" />
</LinearLayout>
<ListView
android:id="@+id/mylist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/textView1"
android:layout_marginTop="26dp" >
</ListView>
</de.mobilej.widget.TopDrawer>
</LinearLayout>
</code></pre>
<br />
<br />
The important things here are the IDs header and myList (which are not wisely choosen I have to admit).<br />
<br />
Wth this idea also things like the Pull-To-Refresh as seen in the latest GMail for Android version are now very easy to implement.<br />
<br />Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com1tag:blogger.com,1999:blog-8650010175198981148.post-21906378111244539842013-05-03T08:06:00.000+02:002013-05-03T08:06:15.442+02:00Continuations for AndroidIn simple words using "Continuations" mean you can pause the execution of code and restart at the same point later with all local variables. (You can think of it as "freezing" the stack)<br />
<br />
While you can use threads and locks for a similar behavior but you can even persist a continuation and reload it later. While this is not something you need every day it can come handy sometimes.<br />
<br />
While other programming languages have built in support for something like this it is not a standard feature of Java.<br />
<br />
However there is a small and nice Apache project making this possible with Java:<br />
<br />
<a href="http://commons.apache.org/sandbox/javaflow/">http://commons.apache.org/sandbox/javaflow/</a><br />
<br />
It comes with an Ant task to do byte code manipulation and a small runtime (one or two classes). Really nice!<br />
<br />
I wondered if this could work on Android, too. (Ok - there is no reason why not but I wanted to give it a try)<br />
<br />
YES! IT WORKS!<br />
<br />
One thing that one should be aware of is that you really need byte code transformation done by an Ant task. While it's no problem to extend Android's ant builds we all need to run code right from Eclipse.<br />
<br />
Fortunately there is a simple solution to this: Just create an Ant script doing whatever is needed and add a new "Builder" between "Java Builder" and "Android Package Builder". That's it!<br />
<br />
<br />Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com3tag:blogger.com,1999:blog-8650010175198981148.post-65086059185430044082013-05-03T07:46:00.002+02:002013-05-03T07:46:41.643+02:00Rethinking Android Unit TestingAfter using <a href="http://pivotal.github.io/robolectric/">Robolectric </a>for a while I was looking for a different approach to do unit testing for Android.<br />
<br />
While Robolectric met a lot of my requirements I had the need for a powerful mocking framework and decided to use PowerMock(ito). It's a really powerful framework but I wasn't able to use it together with Robolectric since both do a lot of classloader and byte code transformation magic.<br />
<br />
So I decided to just run the test with "android.jar" from the SDK on the classpath and forget about Robolectric but instead mock the platform API calls with PowerMock(ito).<br />
<br />
Unfortunately it turned out that this wasn't as easy as it sounds. So I decided to use <a href="http://www.jboss.org/javassist">Javassist</a> to do some byte code manipulation to the android.jar before using it for unit testing. The problem with the original android.jar was that each and every method throws a "Sub"-runtime exception and PowerMock(ito) wasn't able to mock super-calls. (imagine an activity as the class-under-test which must call super.onCreate() in it's onCreate() implementation. Since it's not possible to mock the super.onCreate() you are out of luck)<br />
<br />
This approach turned out to be the silver bullet of In-JVM Android unit testing for me.<br />
<br />
Thanks to Javassist the transformation of the android.jar was a simple task and now I'm able to use the full potential of PowerMock(ito) to test Android code in Eclipse and during CI builds - including code coverage and all the other fancy stuff.<br />
<br />Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com2tag:blogger.com,1999:blog-8650010175198981148.post-61285346722618777762011-07-08T15:33:00.000+02:002011-07-08T15:33:14.931+02:00A Weird Problem using View#setTag(int,Object)Recently we had a problem with OutOfMemory exceptions in one of our Android projects.<br />
<br />
The cause of the problem was that we started using <a href="http://developer.android.com/reference/android/view/View.html#setTag%28int,%20java.lang.Object%29">View#setTag(int,Objec)</a> and it wasn't very obvious why this problem happend.<br />
<br />
After looking at the source code of View it turned out that the indexed tags are stored in<br />
<i> private static WeakHashMap<view, sparsearray=""><object>> sTags;</object></view,></i><br />
<br />
Where the key is the view itself and the value are the indexed values. While I have no idea what has caused this design choice it doesn't seem to be a big problem. BUT we stored ViewHolder like structures in it which indirectly referenced the view itself!<br />
<br />
Because of this the key was never removed by the GC because it was still referenced by the value. To understand the issue you have to know the implementation details of View - otherwise you are absolutely lost and there is no hint found in the docs.<br />
<br />
I hope I will always remember that using View#setTag(int,Object) is a tricky thing and I hope I will never ever store a reference to the view itself as a value.Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com1tag:blogger.com,1999:blog-8650010175198981148.post-86466061627959145962011-06-20T17:37:00.000+02:002011-06-20T17:37:38.754+02:00The Out of Memory MonsterRecently I had a lot of trouble with an Android app because of a memory leak. In the end <a href="http://www.eclipse.org/mat/">Eclipse's MAT</a> was very helpful. While the automatic leak detection brought up a lot of false suspects it turned out that the OQL query "select * from instanceof android.app.Activity" was the right solution. (I guess 99% of all leaks inside Android apps are leaking activities.)<br />
<br />
It turned out that somewhere in the code I had two inner classes which should be static with a <a href="http://developer.android.com/reference/java/lang/ref/WeakReference.html">WeakReference </a>to the activity. One class inherited from <a href="http://developer.android.com/reference/android/os/Handler.html">Handler </a>and one was a <a href="http://developer.android.com/reference/android/content/ServiceConnection.html">ServiceConnection</a>. (And I was very surprised because of the later, but <a href="http://code.google.com/p/android/issues/detail?id=6426">http://code.google.com/p/android/issues/detail?id=6426</a> was very helpful.)Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com0tag:blogger.com,1999:blog-8650010175198981148.post-36956953183760462782011-05-20T10:41:00.001+02:002011-05-20T10:49:30.984+02:00yUML looks promisingI just discovered <a href="http://yuml.me/">yUML</a> and it looks quite promising. The idea of completely self-contained URLs describing the diagram is briliant and should work for small diagrams that can be used in JavaDoc, a blog or a bugtracker.<br />
Additionally I found <a href="http://code.google.com/a/eclipselabs.org/p/yumlec/">yumlec</a> because I already thought about creating something like this my own. Sweet.<br />
<br />
Here is an example of that it can do:<br />
<img src="http://yuml.me/diagram/scruffy/class/[Link],%20[LogTextBox1],%20[Marquee],%20[Activity],%20[LogTextBox],%20[TextView],%20[Activity]%5E-[LogTextBox1],%20[Activity]%5E-[Marquee],%20[Activity]%5E-[Link],%20[TextView]%5E-[LogTextBox],%20[LogTextBox1]-mText%20%3E0..1[LogTextBox]" />Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com0tag:blogger.com,1999:blog-8650010175198981148.post-90976217276722581302011-03-10T19:41:00.001+01:002011-03-10T19:43:20.690+01:00TDD and AndroidI really like TDD and I really love Android but in the past I had some trouble bringing both together.<br />
While Android has nice support for running unit tests on device (or emulator) this isn't really an option for doing TDD since the turnaround times are quite too high. And it's also no fun for the CI server to bring up emulator instances on every build.<br />
<br />
Now I stumbled upon <a href="http://pivotal.github.com/robolectric/">Robolectric</a>. I haven't had a chance to give it a try but if it's half as nice as it seems then it's really everything I ever dreamed of. (in terms of Android TDD developement ...)<br />
<br />
I'm very curious and keen to give it a try.Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com0tag:blogger.com,1999:blog-8650010175198981148.post-12923374391780320652011-02-24T21:49:00.000+01:002011-02-24T21:49:41.193+01:00Android's Main ThreadAndroid's main thread is the the most important thread for your Android app. Not only the UI stuff is done on the main thread but also your services and much more is running on the main thread.<br />
<br />
It's really important not to block the main thread longer than it's absolutely needed. It's always a good idea to not access the network and the filesystem on the main thread. Because of that the nice guys at Google brought us the StrictMode in Android 2.3 to see if your app does IO on the main thread.<br />
<br />
But it's also a bad idea to do anything else that is time consuming on the main thread e.g. when handling the onClick of a button or something similar. You can easily spot those long running tasks on the main thread by using the following code. You should run it as early as possible in your application (Application#onCreate is a good place) and you should consider removing it from release builds:<br />
<br />
<span class="Apple-style-span" style="border-collapse: collapse;"></span><br />
<div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> Looper.<i>getMainLooper</i>().<wbr></wbr>setMessageLogging(</span><b><span lang="EN-US" style="color: #7f0055; font-family: Consolas; font-size: 12pt;">new</span></b><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> Printer() {</span><span lang="EN-US" style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> </span><b><span lang="EN-US" style="color: #7f0055; font-family: Consolas; font-size: 12pt;">long</span></b><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> </span><span lang="EN-US" style="color: #0000c0; font-family: Consolas; font-size: 12pt;">time</span><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> = -1;</span><span lang="EN-US" style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> String </span><span lang="EN-US" style="color: #0000c0; font-family: Consolas; font-size: 12pt;">info</span><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> = </span><b><span lang="EN-US" style="color: #7f0055; font-family: Consolas; font-size: 12pt;">null</span></b><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;">;</span><span lang="EN-US" style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> </span><span lang="EN-US" style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> </span><span lang="EN-US" style="color: #646464; font-family: Consolas; font-size: 12pt;">@Override</span><span lang="EN-US" style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> </span><b><span lang="EN-US" style="color: #7f0055; font-family: Consolas; font-size: 12pt;">public</span></b><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> </span><b><span lang="EN-US" style="color: #7f0055; font-family: Consolas; font-size: 12pt;">void</span></b><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> println(String line) {</span><span lang="EN-US" style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> </span><b><span lang="EN-US" style="color: #7f0055; font-family: Consolas; font-size: 12pt;">if</span></b><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;">(line.startsWith(</span><span lang="EN-US" style="color: #2a00ff; font-family: Consolas; font-size: 12pt;">">>>"</span><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;">)) {</span><span lang="EN-US" style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> </span><span lang="EN-US" style="color: #0000c0; font-family: Consolas; font-size: 12pt;">time</span><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> = System.<i>currentTimeMillis</i>();</span><span lang="EN-US" style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> </span><span lang="EN-US" style="color: #0000c0; font-family: Consolas; font-size: 12pt;">info</span><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> = line;</span><span lang="EN-US" style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> } </span><b><span lang="EN-US" style="color: #7f0055; font-family: Consolas; font-size: 12pt;">else</span></b><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> {</span><span lang="EN-US" style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> </span><b><span lang="EN-US" style="color: #7f0055; font-family: Consolas; font-size: 12pt;">long</span></b><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> now = System.<i>currentTimeMillis</i>();</span><span lang="EN-US" style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> </span><b><span lang="EN-US" style="color: #7f0055; font-family: Consolas; font-size: 12pt;">if</span></b><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;">(now-</span><span lang="EN-US" style="color: #0000c0; font-family: Consolas; font-size: 12pt;">time</span><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;">>500) {</span><span lang="EN-US" style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;"> Log.<i>d</i>(</span><i><span lang="EN-US" style="color: #0000c0; font-family: Consolas; font-size: 12pt;">TAG</span></i><span lang="EN-US" style="color: black; font-family: Consolas; font-size: 12pt;">,</span><span lang="EN-US" style="color: #2a00ff; font-family: Consolas; font-size: 12pt;">"!!!!!!!!!!!! </span><span style="color: #2a00ff; font-family: Consolas; font-size: 12pt;">Too slow:"</span><span style="color: black; font-family: Consolas; font-size: 12pt;">+</span><span style="color: #0000c0; font-family: Consolas; font-size: 12pt;">info</span><span style="color: black; font-family: Consolas; font-size: 12pt;">);</span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="color: black; font-family: Consolas; font-size: 12pt;"> }</span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="color: black; font-family: Consolas; font-size: 12pt;"> }</span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="color: black; font-family: Consolas; font-size: 12pt;"> </span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="color: black; font-family: Consolas; font-size: 12pt;"> }</span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="color: black; font-family: Consolas; font-size: 12pt;"> </span><span style="font-family: Consolas; font-size: 12pt;"></span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="color: black; font-family: Consolas; font-size: 12pt;"> });</span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="color: black; font-family: Consolas; font-size: 12pt;"><br />
</span></div><div class="MsoNormal" style="font-family: arial, sans-serif; font-size: 13px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="color: black; font-family: Consolas; font-size: 12pt;">With this code you will see a log entry every time you are doing anything on the main thread that takes more than 500 miliseconds. One nice thing about this approach is that it doesn't need one of the newer Android versions (all it uses is API Level 1!).</span></div>Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com0tag:blogger.com,1999:blog-8650010175198981148.post-89852915963587191342010-07-23T15:45:00.000+02:002010-07-23T15:45:53.712+02:00Now I also have to complain about the Android MarketUntil today I was really happy with the Android Market. But until now I just was a user of the Android Market. But what happened?<br />
<br />
Today I opened a developer account for the Android Market. My plan was to offer a small little app in two versions: Free and paid. It's quite common in the Android Market and I have seen something like this many times in the Market.<br />
<br />
I really wished that the Market had something similar to <a href="http://www.handango.com/">Handango</a> where you can upload a free "demo" version and the "full version" that a paying customer gets together. Unfortunately this isn't possible!<br />
<br />
And even worse: You even cannot have two apps that use the same name space. (the package attribute of the Manifest file). That means you cannot easily create a free and a paid version from the same code base.<br />
<br />
There are already some ideas how to work around this but all of them are less than ideal.<br />
<br />
See <a href="http://stackoverflow.com/questions/1222302/android-multiple-custom-versions-of-the-same-app">http://stackoverflow.com/questions/1222302/android-multiple-custom-versions-of-the-same-app</a> and <a href="http://droidin.net/2009/05/13/android-multiapp/">http://droidin.net/2009/05/13/android-multiapp/</a> for more about that.Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com6tag:blogger.com,1999:blog-8650010175198981148.post-1034201080875092892010-07-19T10:39:00.000+02:002010-07-19T10:39:22.472+02:00Design Pattern Oriented Design is a Bad Thing!Recently I stumbled upon a post on dzone.com: <a href="http://www.dzone.com/links/r/evil_design_patterns_when_using_too_many_design_p.html">When Design Patterns are Evil</a> and I couldn't agree more.<div><br />
We all know design patterns and we love them. They make it easier to talk about the design of our code and that's what I think they are good for.<br />
<br />
But occasionally I had the joy of maintaining code that seemed to be build around design patterns completely. At first I was impressed (when reading the design documents). But when digging deeper into the code it became more and more obvious that thinking in patterns is a bad idea.<br />
<br />
A pattern in my eyes is just a name for something that a good programmer should come up with when solving a particular problem even if he didn't knew about that pattern before. I often had this situation: Solving a problem by writing code and later recognizing that I used a specific pattern.<br />
<br />
But trying to throw patterns on every problem you encounter will result in over complicated, bloated and over engineered code.<br />
<div><br />
</div><div>Maybe it's some sort of uncertainty that leads developers to over use design patterns. They think: If I can refer to a pattern it can't be wrong.</div></div><div><br />
</div><div>Don't get me wrong: Design patterns are not a bad thing but over using them can be dangerous. As everything in life it's all about balance.</div>Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com0tag:blogger.com,1999:blog-8650010175198981148.post-32067330689425599552010-05-29T12:18:00.000+02:002010-05-29T12:18:31.904+02:00Are Code Reviews just a waste of Time?During the last 15 years I worked for a few different organisations of different sizes.<br />
<br />
Some of them had more or less formal code reviews as part of their software development process. In my current job for example we have a very rigid code review process that says that every bit of code should get reviewed. But why?<br />
<br />
I guess code reviews are considered as a way to improve code quality. It's believed to have a learning effect for less experienced developers as they have to review other's code and their code is reviewed and they should get valuable feedback on their code.<br />
<br />
That's all nice in theory but my experience is different.<br />
<br />
In a real world project it's unlikely that you have enough understanding of other parts of the code to do really useful code reviews. (I know collective code ownership is something that we should try to aspire for but again - I have still to see that.)<br />
<br />
So most code reviews end up with advises like: add some JavaDoc there, don't forget to cleanup imports and format the code (Yes. I know what save actions are for. Thank you!)<br />
<br />
All of this stuff (and much more) can be done easily by <a href="http://pmd.sourceforge.net/">PMD</a>, <a href="http://findbugs.sourceforge.net/">FindBugs</a> and <a href="http://checkstyle.sourceforge.net/">CheckStyle</a>. Done properly by an continuous integration server it's automatic and easy. Additionally it's much easier to accept feedback like this from an automatic system than it's to accept from the JavaDoc Nazi next door.<br />
<br />
The saved time can be invested into stuff that really improves the code. You could start to do pair programming for at least the important modules. That's really something that creates collective code ownership and makes it possible to have more than one person that really know what's going on inside these core parts. Additionally less experienced developer can learn from the other's and the experienced developers learn how the other's think and code.<br />
<br />
But I'm still trying to get this into the heads of my coworkers and managers.Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com2tag:blogger.com,1999:blog-8650010175198981148.post-44101041768559638402010-05-08T18:15:00.001+02:002010-07-23T15:47:01.717+02:00My two favorite programming related booksThere are a lot of programming related books available. Some are good, some are - well - not so good.<br />
<div><br />
</div><div>But there are two books that I really, really like.</div><div><br />
</div><div>The first one (my number one) is <a href="http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882/ref=sr_1_1?ie=UTF8&s=books&qid=1273333989&sr=8-1">"Clean Code: A Handbook of Agile Software Craftsmanship"</a>.</div><div><br />
</div><div>There was nothing totally new in there for me but I personally agree with 99% of everything the author says. It's not a book that teaches you how to write code but it tells you how to write good code. My favorite statement is that software development is a craft, not a science and that's what I ever believed. The only problem was that it seemed that nobody else was thinking the same. But fortunately the craftsmanship thing is now quite popular and we all know by now that we should be proud of being a good craftsman. There clearly is a demand for computer science but software development is something totally different. So in my opinion: "software development != computer science". And this book helps you to become a good craftsman.</div><div><br />
</div><div>The other book is <a href="http://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670/ref=sr_1_1?ie=UTF8&s=books&qid=1273334508&sr=1-1">"Code Complete: A Practical Handbook of Software Construction"</a>.</div><div><br />
</div><div>I think it's much more low level than the other book. If "Clean Code" is about writing good code this book is just about writing code that actually works. It's a lot of stuff that everyone should already know but I think it's good to have a book like this. While you are reading the book you always think "Hey, I recall this one".</div><div><br />
</div>Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com0tag:blogger.com,1999:blog-8650010175198981148.post-47028284805098042312010-04-26T21:36:00.000+02:002010-04-26T21:36:10.441+02:00Experiencing an API bug the hard wayWe (my wife, my daughter and I) enjoy <a href="http://www.geocaching.com/">Geocaching</a> a lot. It's no surprise that I am using my own (Android) application for that. It's far from complete and it's just for my personal use but last weekend we wanted to show our neighbors how much fun geocaching is and I started to enter the first coordinate into my app and .... got an error.<br />
<br />
I entered a valid coordinate like "7:59,149" and the app told me it's invalid.<br />
<br />
What happened?<br />
<br />
The class android.location.Location#convert(String) is buggy. It's not a big thing but it's really annoying. In the end I just copied the method from the Android sources into my own class and changed the validation condition. So my app is working again.<br />
<br />
But learning about this bug this way I will always remember it. (It was so embarrassing!)Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com0tag:blogger.com,1999:blog-8650010175198981148.post-72824508512715533122010-01-31T18:54:00.001+01:002010-01-31T19:04:18.194+01:00Viewing DZone on the Android Browser<span class="Apple-style-span" style="font-family: Arial; font-size: small;"><span class="Apple-style-span" style="font-size: 13px;"><span class="Apple-style-span" style="font-family: 'Times New Roman';"><span class="Apple-style-span" style="font-size: medium;">I really like my new job I started in January 2010 but the way to work takes a lot of time. I travel by train so I can use my G1 to surf the web and do stuff like that so the time isn't completly wasted.</span></span></span></span><br />
<span class="Apple-style-span" style="font-family: Arial; font-size: small;"><span class="Apple-style-span" style="font-size: 13px;"><span class="Apple-style-span" style="font-family: 'Times New Roman';"><span class="Apple-style-span" style="font-size: medium;"><br />
</span></span></span></span><br />
<span class="Apple-style-span" style="font-family: Arial; font-size: small;"><span class="Apple-style-span" style="font-size: 13px;"><span class="Apple-style-span" style="font-family: 'Times New Roman';"><span class="Apple-style-span" style="font-size: medium;">One of the websites I really want to read is <a href="http://www.dzone.com/">DZone.com</a>. Unfortunately it doesn't look good on mobile browsers.</span></span></span></span><br />
<span class="Apple-style-span" style="font-family: Arial; font-size: small;"><span class="Apple-style-span" style="font-size: 13px;"><span class="Apple-style-span" style="font-family: 'Times New Roman';"><span class="Apple-style-span" style="font-size: medium;"><br />
</span></span></span></span><br />
<span class="Apple-style-span" style="font-family: Arial; font-size: small;"><span class="Apple-style-span" style="font-size: 13px;"><span class="Apple-style-span" style="font-family: 'Times New Roman';"><span class="Apple-style-span" style="font-size: medium;">So I hacked a simple iframe to make it work (at least a little bit better) on my Android phone.</span></span></span></span><br />
<span class="Apple-style-span" style="font-family: Arial; font-size: small;"><span class="Apple-style-span" style="font-size: 13px;"><span class="Apple-style-span" style="font-family: 'Times New Roman';"><span class="Apple-style-span" style="font-size: medium;"><br />
</span></span></span></span><br />
<span class="Apple-style-span" style="font-family: Arial; font-size: small;"><span class="Apple-style-span" style="font-size: 13px;"><span class="Apple-style-span" style="font-family: 'Times New Roman';"><span class="Apple-style-span" style="font-size: medium;">To access DZone from the mobile browser just open this link in your mobile browser:</span></span></span></span><br />
<span class="Apple-style-span" style="font-family: Arial; font-size: small;"><span class="Apple-style-span" style="font-size: 13px;"><span class="Apple-style-span" style="font-family: 'Times New Roman';"><span class="Apple-style-span" style="font-size: medium;"><br />
</span></span></span></span><br />
<span class="Apple-style-span" style="font-family: Arial; font-size: small;"><span class="Apple-style-span" style="font-size: 13px;"><span class="Apple-style-span" style="font-family: 'Times New Roman';"><span class="Apple-style-span" style="font-size: medium;"><a href="http://www.mobile-j.de/dzone.html">http://www.mobile-j.de/dzone.html</a></span></span></span></span><br />
<span class="Apple-style-span" style="font-size: large;"><span class="Apple-style-span" style="font-size: small;"><span class="Apple-style-span" style="font-size: 13px;"><br />
</span></span></span><br />
<span class="Apple-style-span" style="font-family: Arial; font-size: small;"><span class="Apple-style-span" style="font-size: 13px;"><span class="Apple-style-span" style="font-family: 'Times New Roman';"><span class="Apple-style-span" style="font-size: medium;">It's not perfect but better than nothing. It might also work on other mobile browser but I haven't tested this. </span></span></span></span><br />
<span class="Apple-style-span" style="font-family: Arial; font-size: small;"><span class="Apple-style-span" style="font-size: 13px;"><span class="Apple-style-span" style="font-family: 'Times New Roman';"><span class="Apple-style-span" style="font-size: medium;"><br />
</span></span></span></span><br />
<span class="Apple-style-span" style="font-family: Arial; font-size: small;"><span class="Apple-style-span" style="font-size: 13px;"><span class="Apple-style-span" style="font-family: 'Times New Roman';"><span class="Apple-style-span" style="font-size: medium;"><br />
</span></span></span></span>Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com0tag:blogger.com,1999:blog-8650010175198981148.post-52249640731416397202010-01-25T21:13:00.000+01:002010-01-25T21:13:35.570+01:00AlarmManager vs Task KillerFor my current private coding@home project I was looking at how to schedule alarms on Android.<br />
<br />
I quickly realized that the AlamManager was what I was looking for. I followed one of the many resources found on the net and started to implement a simple test application. One of those pages explaining how to deal with AlarmManager and WakeLocks is <a href="http://www.androidguys.com/2009/04/02/wake-up-with-the-alarm/">http://www.androidguys.com/2009/04/02/wake-up-with-the-alarm/</a>.<br />
<br />
Everything was fine and it instantly worked. BUT! I wanted to see if it will also work when my app is stopped (e.g. when Android runs out of memory it will stop apps that are still running in the background). To make it easy I took one of those task killer apps you can find in the Android market. I killed my app after the alarm was set and ..... the alarm never got off.<br />
<br />
I was really disappointed and was looking for a solution. I also had a look or two at the built-in Alarm Clock app. I did everything like it was done there. And still it wasn't working.<br />
<br />
Then I tried what happens to the Alarm Clock when I create an alarm and kill the app. And what happened? Exactly the same. I couldn't believe what I had seen.<br />
<br />
After some googling I found that the real problem wasn't the alarm stuff but the task killer itself. ( see <a href="http://groups.google.com/group/android-developers/browse_thread/thread/3f87972d1f99ee81?pli=1">http://groups.google.com/group/android-developers/browse_thread/thread/3f87972d1f99ee81?pli=1</a> )<br />
<br />
I really wasn't aware that using a task killer on Android is so aggressive. By definition it means killing also the alarms registered by the app (and everything else).<br />
<br />
Now I know better and don't use that task killer app so much as I did before because now I can imagine how much can go wrong when using those kind of apps. (And now I know why there is no such app preinstalled on the device.)Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com0tag:blogger.com,1999:blog-8650010175198981148.post-60041889531806158142010-01-12T21:19:00.000+01:002010-01-12T21:19:05.399+01:00J2ME Unit TestingIn the past I tried to get happy with unit testing of J2ME code.<br />
<br />
I used JMUnit and J2MEUnit. JMUnit is a lot more straight forward while J2MEUnit is a high ceremony framework which requires a lot of boiler plate code just to be as much like JUnit as possible.<br />
<br />
But in the end both suck. All J2ME testing frameworks will suck.<br />
<br />
Why? Because unit testing should be easy and fun but compile, preverify, deploy to the emulator or the device, doing some manual steps just to see that <i>something</i> is broken isn't fun and it's also not easy.<br />
<br />
There is a lot of stuff you have to try on the phone but that has nothing to do with unit testing but it's tinkering around implementation issues. You will have to do that anyway and I guess both frameworks are of little help here.<br />
<br />
So what is the answer to J2ME unit testing?<br />
<br />
In my opinion the best approach is to do unit testing on the PC inside the IDE. Using JUnit 4 and Java SE.<br />
<br />
I'm currently giving this solution a try. I have a separate project with a dependency on the J2ME project.<br />
<br />
For all those J2ME classes you need stubs. I haven't found usable code on the net so I write them myself. (I don't have a lot of them yet)<br />
<br />
The good thing is you can really take control over everything with your own implementation of the J2ME classes. Your own bluetooth connection, file connection ... you name it. It's a lot of work but you can do almost anything. Once you have your mocks and stubs in place you will see how much better this approach is.<br />
<br />
But the best about this is that the tests run fast, you can exhaust all the cool stuff from your IDE from good debugging support to integrated JUnit support, you can use mocking frameworks like Mockito, EasyMock etc., you can use a modern framework (JUnit 4) and you could even use coverage analysis integrated into your IDE (but I haven't tried it yet).<br />
<br />
It should also be easy to integrate this into the build and run the tests on the CI server. (This is a nightmare with the "pure" J2ME solution.)<br />
<br />
<br />
<div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">This idea is inspired by an <a href="https://sites.google.com/site/androiddevtesting/">experimental approach to unit testing on Android</a> which is something I will try on one of my next Android projects.<br />
</div>Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com2tag:blogger.com,1999:blog-8650010175198981148.post-18361743029338545622010-01-09T16:37:00.002+01:002010-01-09T16:38:50.469+01:00Nokia S40 SDK on Linux<span style="font-family: inherit;">I completely switched to Linux some months ago and I'm quite happy with it.</span><br />
<span style="font-family: inherit;">Unfortunately there is still a lot of Windows only software. For the most stuff there is a free and open equivalent but when it comes to mobile (J2ME) SDKs / emulators the only way to go is usually the old(!) WTK (2.5.2).</span><br />
<span style="font-family: inherit;">I just wanted to share the information that some really nice guy at the Forum Nokia Discussion Board told us how to run the S40 SDK on Wine.</span><br />
<span style="font-family: inherit;">See: <a href="http://discussion.forum.nokia.com/forum/showthread.php?t=90868">http://discussion.forum.nokia.com/forum/showthread.php?t=90868</a> (Scroll down to </span><span style="-webkit-border-horizontal-spacing: 1px; -webkit-border-vertical-spacing: 1px;"><span style="color: black;"><span style="font-family: inherit;">yanng34</span></span><span style="font-family: inherit;">'s post).</span></span><br />
<span style="-webkit-border-horizontal-spacing: 1px; -webkit-border-vertical-spacing: 1px;"><span style="font-family: inherit;">I successfully tested the solution on Ubuntu 9.10 with the Series 40 6th Edition SDK.</span></span><br />
<span style="-webkit-border-horizontal-spacing: 1px; -webkit-border-vertical-spacing: 1px; color: #444433; font-family: Arial, Helvetica, sans-serif, 宋体; font-size: 14px; font-weight: bold;"> </span>Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com3tag:blogger.com,1999:blog-8650010175198981148.post-71625408961657515802009-12-28T16:18:00.000+01:002009-12-28T16:18:40.025+01:00I really like Android's EditTextIn the last couple of days I enjoyed doing a little bit coding for Android at home. With a J2ME background I was really amazed by the richness of the Android APIs. You have a lot of the Java Standard Edition APIs plus a lot of useful and well thought out APIs for UI and phone related stuff.<br />
<br />
I quickly realized that a lot of functionality I thought I have to implement myself is already there. For example I really like the preferences API. On J2ME I had to implement everything myself. Starting from the preferences UI to the preference store.<br />
<br />
Also surprising for me was how rich the widgets really are. Let's take the <a href="http://developer.android.com/reference/android/widget/EditText.html">EditText</a> widget. It not only looks really good when rendered on the screen it also has a lot of functionality you can easily use.<br />
<br />
For my first application I have a form to enter geocoordinates. They are represented as text like "50:24,123".<br />
<br />
After the basics worked I wanted to make the UI a little bit more user friendly by helping the user to enter only valid coordinates.<br />
<br />
First I wanted to limit the usable characters to "0123456789:,.-". And it turned out to be quite simple:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>NumberKeyListener keyListener = new NumberKeyListener() {
public int getInputType() {
return InputType.TYPE_CLASS_NUMBER;
}
@Override
protected char[] getAcceptedChars() {
return new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', ':', '-', ',' };
}
};
lonEditText.setKeyListener(keyListener);
</code></pre><br />
But this still doesn't prevent someone from entering something like "1:1:1:1" which is obviously not valid. So I introduced an input filter:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code> InputFilter[] filters = new InputFilter[1];
filters[0] = new InputFilter() {
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
if (end > start) {
String destTxt = dest.toString();
String resultingTxt = destTxt.substring(0, dstart) + source.subSequence(start, end) + destTxt.substring(dend);
if (!resultingTxt.matches("[0123456789]*[\\:]?[0123456789]*[,.]?[0123456789]*")) {
if (source instanceof Spanned) {
SpannableString sp = new SpannableString("");
return sp;
} else {
return "";
}
}
}
return null;
}
};
lonEditText.setFilters(filters);
</code></pre><br />
(Please note that this is just an example and the code isn't production quality.)<br />
<br />
That was easy and a lot better. Unfortunately you can still enter invalid coordinates like "999:99.123".<br />
<br />
First I thought of getting all the magic into the regexp but first I don't like regexp-magic so much and second I wanted to see how I can give the user some feedback about the validity of the entered data.<br />
<br />
I decided to implement a <a href="http://developer.android.com/reference/android/text/TextWatcher.html">TextWatcher</a> and validate the data in "afterTextChanged".<br />
<br />
There I just tried to parse the coordinate and in case of an error I wanted to inform the user. My first approach was to use a Toast but it turned out that EditText already has everything I needed.<br />
<br />
The code looked like this:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code> if(coordinateValid){
lonEditText.setError(null);
} else {
lonEditText.setError("Coordinate is invalid.");
}
</code></pre><br />
This is not the actual source but it shows what I wanted to show: Use setError to indicate a problem with the text contained in that EditText.<br />
<br />
When the invalid field has focus the error is displayed. If it's not focused there is an icon showing that something is wrong with the content of this field.<br />
<br />
That was easy and it's more than I had expected. Thanks Android!Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com5tag:blogger.com,1999:blog-8650010175198981148.post-11439883578984986462009-12-24T12:30:00.002+01:002009-12-28T15:37:03.794+01:00Fragmentation is inevitableMany J2ME developers are complaining about device fragmentation. And I agree that it's one of the hardest challenges when developing portable MIDlets.<br />
<br />
Recently similar things are said about Android. (see <a href="http://androidandme.com/2009/11/news/what-does-android-fragmentation-look-like/">http://androidandme.com/2009/11/news/what-does-android-fragmentation-look-like/</a> )<br />
<br />
And even on the iPhone there are similar problems. And I think that those problems are inevitable when you don't want to stick to old and outdated technology.<br />
<br />
But why does fragmentation occur? What are the reasons for it?<br />
<br />
<ul><li>Different software / hardware vendors. Even if the specs are very similar the details of an implementation can make the difference. This affects J2ME a lot. Luckily Android devices are more similar in terms of OS implementation but the hardware varies. The iPhone is the clear winner here. There's only one vendor. (Well this is in my opintion one of the few things that are good about having one vendor only.)</li>
<li>Software evolution. (i.e. new versions) Software normally gets better and better with each revision and usually users want to use the latest features. But not all users can or want to update their devices. So you have to deal with multiple versions.</li>
<li>Hardware evolution. New devices have higher screen resolutions, more memory, more sensors etc. If you want to make use of those new capabilities and don't want to break compatability with less powerful devices you have to write smart applications.</li>
</ul><br />
In the end device fragmentation is the consequence of device evolution. And we have to live with it because it won't go away.<br />
<br />
Even controlled platforms like the iPhone are affected by this. Not as much as others but even iPhone developers have to deal with it. The next big change to that platform might be the introduction of new HD models with higher screen resolutions. We will see how those developers can deal with it. On other platforms people are already used to different screen sizes but I guess only few iPhone developers today think about screen size.Bjoernhttp://www.blogger.com/profile/17529062050741717766noreply@blogger.com0