Search This Blog

25 July 2010

ClassNotFoundException: org.eoti.MyAuthenticationService in loader dalvik.system.PathClassLoader

I came across an annoying thing today...  I install my app and use it.  I close it and reopen it a few times.  I use other things then come back and use it.  Everything is working fine.

Then I reboot.  Now, every time it tries to use the authentication mechanism, it force closes.  On the phone, it doesn't give any details at all in logCat.  On the emulator, I can't replicate it.

Or can I?  I go into Manage Applications and force close the app.  Launch it and voila, same thing.

Specifically, Account Manager's blockingGetAuthToken is trying to load the authentication class (which it used not more than 30 seconds ago) and I get this:
(slightly cleaned up stack)
W/dalvikvm(  550): threadid=1: thread exiting with uncaught exception (group=0x4001d800)
E/AndroidRuntime(  550): FATAL EXCEPTION: main
E/AndroidRuntime(  550): java.lang.RuntimeException: Unable to instantiate service org.eoti.MyAuthenticationService: java.lang.ClassNotFoundException: org.eoti.MyAuthenticationService in loader dalvik.system.PathClassLoader[/data/app/org.eoti.test-1.apk]
E/AndroidRuntime(  550):        at android.app.ActivityThread.handleCreateService(ActivityThread.java:2943)
E/AndroidRuntime(  550):        at android.app.ActivityThread.access$3300(ActivityThread.java:125)
E/AndroidRuntime(  550):        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2087)
E/AndroidRuntime(  550):        at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime(  550):        at android.os.Looper.loop(Looper.java:123)
E/AndroidRuntime(  550):        at android.app.ActivityThread.main(ActivityThread.java:4627)
E/AndroidRuntime(  550):        at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(  550):        at java.lang.reflect.Method.invoke(Method.java:521)
E/AndroidRuntime(  550):        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
E/AndroidRuntime(  550):        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
E/AndroidRuntime(  550):        at dalvik.system.NativeStart.main(Native Method)
E/AndroidRuntime(  550): Caused by: java.lang.ClassNotFoundException: org.eoti.MyAuthenticationService in loader dalvik.system.PathClassLoader[/data/app/org.eoti.test-1.apk]
E/AndroidRuntime(  550):        at dalvik.system.PathClassLoader.findClass(PathClassLoader.java:243)
E/AndroidRuntime(  550):        at java.lang.ClassLoader.loadClass(ClassLoader.java:573)
E/AndroidRuntime(  550):        at java.lang.ClassLoader.loadClass(ClassLoader.java:532)
E/AndroidRuntime(  550):        at android.app.ActivityThread.handleCreateService(ActivityThread.java:2940)
E/AndroidRuntime(  550):        ... 10 more
I/Process (  550): Sending signal. PID: 550 SIG: 9
I/ActivityManager(   59): Process org.eoti.test:main (pid 550) has died.

Ok... so it is able to load the classes... until a reboot (or force close)... and then it can't find them anymore?  If anyone has any thoughts on the matter, I'd be keen to hear them...

18 July 2010

Intent Filters

Let's start with a basic app:

F:\work> mvn archetype:generate -DarchetypeCatalog=http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml
2: http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml -> galatea-archetype (null)
Choose a number: : 2
groupId: org.eoti.android.tutorial.intent
artifactId: IntentTest
version: 1.0-SNAPSHOT
package: org.eoti.android.tutorial.intent

F:\work> cd IntentTest
F:\work\IntentTest> mvn clean install


Add this into your IntentTestActivity:
    public void customClickHandler1(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/param1/param2")));
    }
   
Add this into the end of your LinearLayout in main.xml:
    <Button
        android:text="Click Me #1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="customClickHandler1" />

Following this methodology, we can have custom methods rather than multiple if/else.


Create a new activity:
package org.eoti.android.tutorial.intent;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class SubActivity extends Activity
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sub);
        ((TextView)findViewById(R.id.text)).setText(getClass().getSimpleName() + " loaded: " + getIntent().getData());
    }
}



And create the res\layout\sub.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/text"
        />
</LinearLayout>


And add it to the AndroidManifest.xml:
        <activity android:name=".SubActivity">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="custom1" android:host="localhost" android:pathPattern="/param1/.*" />
            </intent-filter>
        </activity>


Redeploy (mvn clean install)

Ok, that all works well... let's try having different path prefixes...

Add another button:
Update main.xml:
    <Button
        android:text="Click Me #2"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="customClickHandler2" />

Update IntentTestActivity:
    public void customClickHandler2(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/paramA/paramB")));
    }

Create SubActivity2:
package org.eoti.android.tutorial.intent;

import android.os.Bundle;

public class SubActivity2 extends SubActivity
{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

Update AndroidManifest.xml:
        <activity android:name=".SubActivity2">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="custom1" android:host="localhost" android:pathPattern="/paramA/.*" />
            </intent-filter>
        </activity>

Redeploy (mvn clean install)... Now, we can launch either one by the first part of the URL...  what about something at the end?


Make a new activity:
package org.eoti.android.tutorial.intent;

import android.os.Bundle;

public class SubActivity3 extends SubActivity
{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

        <activity android:name=".SubActivity3">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="custom1" android:host="localhost" android:pathPattern="/paramA/.*/paramC" />
            </intent-filter>
        </activity>

    <Button
        android:text="Click Me #3"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="customClickHandler3" />

    public void customClickHandler3(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/paramA/paramB/paramC")));
    }

Redeploy (mvn clean install)... what happens?  #1 and #2 work; but #3 asks you how you want to handle it (the picker is making you choose between #2 and #3).  That's because the 3rd url matches both intent-filters.  How do we fix that?

We don't have access to the full regex library (almost none of it really), so the simplest way is to make it behave more like files and directories...

Let's make "/paramA/paramB" more like a directory by appending a slash (ie: "/paramA/paramB/")
    public void customClickHandler2(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/paramA/paramB/")));
    }

        <activity android:name=".SubActivity2">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="custom1" android:host="localhost" android:pathPattern="/paramA/.*/" />
            </intent-filter>
        </activity>

Redeploy (mvn clean install). Now what happens? All three of them now launch.  We have a gotcha here though.  Try this:

    <Button
        android:text="Broken"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="customClickHandler4" />

    public void customClickHandler4(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/paramA/paramB")));
    }

Redeploy (mvn clean install) and you will see that clicking the "Broken" button will cause a stack trace:
E/AndroidRuntime( 1278): Caused by: android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=custom1://localhost/paramA/paramB }
E/AndroidRuntime( 1278):        at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1408)
E/AndroidRuntime( 1278):        at android.app.Instrumentation.execStartActivity(Instrumentation.java:1378)
E/AndroidRuntime( 1278):        at android.app.Activity.startActivityForResult(Activity.java:2817)
E/AndroidRuntime( 1278):        at android.app.Activity.startActivity(Activity.java:2923)
E/AndroidRuntime( 1278):        ... 15 more
W/ActivityManager(   61):   Force finishing activity org.eoti.android.tutorial.intent/.IntentTestActivity

This is because we now require a slash to be on the end (or 'paramC').

Let's try one more thing:

    <Button
        android:text="Wrong"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="customClickHandler5" />

    public void customClickHandler5(View target)
    {
        startActivity(new  Intent(Intent.ACTION_VIEW, Uri.parse("custom1://localhost/paramA/paramB/paramC/paramD/")));
    }

Redeploy (mvn clean install).  What happens?  Why doesn't it match "/paramA/.*/" from SubActivity2?  My only guess is that the "everything" in ".*" excludes the "/"?

So end result?  If you are willing to carefully layout your Uris and be limited to ".*" or literals (rather than say '[0-9]+') then this solution MIGHT be cleaner than creating an intent hub to process the strings and start your activities...  too bad we don't have access to true Java regex functionality.

16 July 2010

Tables


Let's start with a basic app...

F:\work> mvn archetype:generate -DarchetypeCatalog=http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml
2: http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml -> galatea-archetype (null)
Choose a number: : 2
groupId: org.eoti.android.table
artifactId: TableTest
version: 1.0-SNAPSHOT
package: org.eoti.android.table

F:\work> cd TableTest
F:\work\TableTest> mvn clean install


Now, let's create a new src/main/android/res/values/styles.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="cell">
        <item name="android:layout_height">32px</item>
        <item name="android:layout_width">32px</item>
    </style>
</resources>

Edit src/main/android/res/layout/main.xml:
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content">

    <TableRow>
        <!-- empty corner -->
        <TextView
            style="@style/cell"
            android:text=" "/>
        <!-- top row -->
        <TextView
            style="@style/cell"
            android:text="C1"/>
        <TextView
            style="@style/cell"
            android:text="C2"/>
        <TextView
            style="@style/cell"
            android:text="C3"/>
    </TableRow>

    <!-- row 1 -->
    <TableRow>
        <!-- row 1: left edge -->
        <TextView
            style="@style/cell"
            android:text="R1"/>
        <!-- row 1, col 1 -->
        <TextView
            style="@style/cell"
            android:text="1x1"/>
        <!-- row 1, col 2 -->
        <TextView
            style="@style/cell"
            android:text="2x1"/>
        <!-- row 1, col 3 -->
        <TextView
            style="@style/cell"
            android:text="3x1"/>
    </TableRow>

    <!-- row 2 -->
    <TableRow>
        <!-- row 2: left edge -->
        <TextView
            style="@style/cell"
            android:text="R2"/>
        <!-- row 2, col 1 -->
        <TextView
            style="@style/cell"
            android:text="1x2"/>
        <!-- row 2, col 2 -->
        <TextView
            style="@style/cell"
            android:text="2x2"/>
        <!-- row 2, col 3 -->
        <TextView
            style="@style/cell"
            android:text="3x2"/>
    </TableRow>

    <!-- row 3 -->
    <TableRow>
        <!-- row 3: left edge -->
        <TextView
            style="@style/cell"
            android:text="R3"/>
        <!-- row 3, col 1 -->
        <TextView
            style="@style/cell"
            android:text="1x3"/>
        <!-- row 3, col 2 -->
        <TextView
            style="@style/cell"
            android:text="2x3"/>
        <!-- row 3, col 3 -->
        <TextView
            style="@style/cell"
            android:text="3x3"/>
    </TableRow>
</TableLayout>

Redeploy (mvn clean install) and you should see a fairly unimpressive table.

Let's see if we can make it look a little better...

Update your styles.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="cell">
        <item name="android:layout_height">32px</item>
        <item name="android:layout_width">32px</item>
        <item name="android:gravity">center</item>
        <item name="android:layout_margin">2dp</item>
    </style>
    <style name="header" parent="@style/cell">
        <item name="android:background">#4D62A4</item>
        <item name="android:textColor">#000000</item>
        <item name="android:textStyle">bold</item>
    </style>
</resources>

And we will change the style of the header column and rows:
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content">

    <TableRow>
        <!-- empty corner -->
        <TextView
            style="@style/header"
            android:text=" "/>
        <!-- top row -->
        <TextView
            style="@style/header"
            android:text="C1"/>
        <TextView
            style="@style/header"
            android:text="C2"/>
        <TextView
            style="@style/header"
            android:text="C3"/>
    </TableRow>

    <!-- row 1 -->
    <TableRow>
        <!-- row 1: left edge -->
        <TextView
            style="@style/header"
            android:text="R1"/>
        <!-- row 1, col 1 -->
        <TextView
            style="@style/cell"
            android:text="1x1"/>
        <!-- row 1, col 2 -->
        <TextView
            style="@style/cell"
            android:text="2x1"/>
        <!-- row 1, col 3 -->
        <TextView
            style="@style/cell"
            android:text="3x1"/>
    </TableRow>

    <!-- row 2 -->
    <TableRow>
        <!-- row 2: left edge -->
        <TextView
            style="@style/header"
            android:text="R2"/>
        <!-- row 2, col 1 -->
        <TextView
            style="@style/cell"
            android:text="1x2"/>
        <!-- row 2, col 2 -->
        <TextView
            style="@style/cell"
            android:text="2x2"/>
        <!-- row 2, col 3 -->
        <TextView
            style="@style/cell"
            android:text="3x2"/>
    </TableRow>

    <!-- row 3 -->
    <TableRow>
        <!-- row 3: left edge -->
        <TextView
            style="@style/header"
            android:text="R3"/>
        <!-- row 3, col 1 -->
        <TextView
            style="@style/cell"
            android:text="1x3"/>
        <!-- row 3, col 2 -->
        <TextView
            style="@style/cell"
            android:text="2x3"/>
        <!-- row 3, col 3 -->
        <TextView
            style="@style/cell"
            android:text="3x3"/>
    </TableRow>
</TableLayout>


Redeploy (mvn clean install).

That's all well and good... but what if we want to do spanning?

Change row 2:
    <!-- row 2 -->
    <TableRow>
        <!-- row 2: left edge -->
        <TextView
            style="@style/header"
            android:text="R2"/>
        <!-- row 2, col 1 -->
        <TextView
            style="@style/cell"
            android:layout_column="1"
            android:layout_span="2"
            android:text="1x2"/>
        <!-- row 2, col 2 -->
        <!--<TextView-->
            <!--style="@style/cell"-->
            <!--android:text="2x2"/>-->
        <!-- row 2, col 3 -->
        <TextView
            style="@style/cell"
            android:text="3x2"/>
    </TableRow>
   
And let's make it a little easier to see where the data cells are. Add this to your @style/cell:
<item name="android:background">#333333</item>

Redeploy (mvn clean install).

Ok, this is starting to look really good.... but... what if I want more than 1 view added to a cell?

Let's replace "row 1, col 2":

    <!-- row 1 -->
    <TableRow>
        <!-- row 1: left edge -->
        <TextView
            style="@style/header"
            android:text="R1"/>
        <!-- row 1, col 1 -->
        <TextView
            style="@style/cell"
            android:text="1x1"/>
        <!-- row 1, col 2 -->
        <LinearLayout
                android:orientation="horizontal"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">
            <ImageView
                style="@style/cell"
                android:src="@drawable/icon"/>
            <ImageView
                style="@style/cell"
                android:src="@drawable/icon"/>
        </LinearLayout>
        <!-- row 1, col 3 -->
        <TextView
            style="@style/cell"
            android:text="3x1"/>
    </TableRow>


Redeploy (mvn clean install). You should now see two icons in one cell.

Last, but not least; what if we want the table to stretch across the screen?

Add this to the outside TableLayout:
android:stretchColumns="*"

Redeploy (mvn clean install) and it should now span width.

10 July 2010

Using the Barcode Scanner

Let's start with a basic app:

F:\work> mvn archetype:generate -DarchetypeCatalog=http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml
2: http://kallisti.eoti.org:8081/content/repositories/snapshots/archetype-catalog.xml -> galatea-archetype (null)
Choose a number: : 2
groupId: org.eoti.android.barcode
artifactId: BarcodeTest
version: 1.0-SNAPSHOT
package: org.eoti.android.barcode
 
F:\work> cd BarcodeTest
F:\work\BarcodeTest> mvn clean install

Now, let's modify it. 

Modify your main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/content"
    />
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/format"
    />
</LinearLayout>

Modify your activity:

package org.eoti.android.barcode;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.text.Html;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;

public class BarcodeTestActivity extends Activity
{
    private static String TAG = "BarcodeTest";
    private static final String INTENT_SCAN = "com.google.zxing.client.android.SCAN";
    private static final String SCAN_MODE = "SCAN_MODE";
    private static final String QR_CODE_MODE = "QR_CODE_MODE";
    private static final String SCAN_RESULT = "SCAN_RESULT";
    private static final String SCAN_RESULT_FORMAT = "SCAN_RESULT_FORMAT";
    private static final int REQCODE_SCAN = 0;
    private Handler handler;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        handler = new Handler();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        Intent intent = new Intent(INTENT_SCAN);
        intent.putExtra(SCAN_MODE, QR_CODE_MODE);
        startActivityForResult(intent, REQCODE_SCAN);
        return true;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        if(requestCode == REQCODE_SCAN)
        {
            set(R.id.content, "<b>Contents: </b>" + data.getStringExtra(SCAN_RESULT));
            set(R.id.format, "<b>Format: </b>" + data.getStringExtra(SCAN_RESULT_FORMAT));
        }else{
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    protected void set(final int id, final String text)
    {
        handler.post(new Runnable(){
            public void run() {
                ((TextView)findViewById(id)).setText(Html.fromHtml("<html><body>" + text + "</body></html>"));
            }
        });
    }
}


Unfortunately, we won't be able to test the rest of it in the emulator.  You will need an actual device for that.

Install it on your phone (I do it by using a webserver, but you could use adb) and launch it.

When the screen comes up, click the screen.  That will launch your Barcode Scanner.

Scan a QR CODE (I scanned this one )

It will go back to your test app. 
"Contents" will be the data content (the market url in the above case) and the "Format" will be QR CODE.

Publishing to the Android Market

I wanted to give publishing a shot, so I made a small tool to browse prime numbers and then started down the publishing notes...

After you finish cleaning up your code (remove debugging logging, etc) and make sure it still works....

Add versioning to your AndroidManifest.xml.  I'm hoping that eventually this piece can be replaced with something that reads the pom.xml... but, in the meantime attach these to the top level manifest:
android:versionCode="1"
android:versionName="1.0"

Then let's add a uses-sdk element:
   
    <uses-sdk android:minSdkVersion="7"
          android:targetSdkVersion="8"/>
Update your pom.xml:
           
               
            <plugin>
                <groupId>com.jayway.maven.plugins.android.generation2</groupId>
                <artifactId>maven-android-plugin</artifactId>
                <version>2.5.0</version>
                <configuration>
                    <androidManifestFile>${project.basedir}/src/AndroidManifest.xml</androidManifestFile>
                    <assetsDirectory>${project.basedir}/src/main/assets</assetsDirectory>
                    <resourceDirectory>${project.basedir}/src/main/android/res</resourceDirectory>
                    <!--<resourceOverlayDirectory>${project.basedir}/src/main/android/overlay</resourceOverlayDirectory>-->
                    <nativeLibrariesDirectory>${project.basedir}/src/main/native</nativeLibrariesDirectory>
                    <sdk><platform>7</platform></sdk>
                    <deleteConflictingFiles>true</deleteConflictingFiles>
                    <undeployBeforeDeploy>true</undeployBeforeDeploy>
                    <sign>
                        <debug>false</debug>
                    </sign>
                </configuration>
                <extensions>true</extensions>
                <executions>
                    <execution>
                        <id>android-undeploy</id>
                        <phase>pre-clean</phase>
                        <goals><goal>undeploy</goal></goals>
                    </execution>
                    <execution>
                        <id>alignApk</id>
                        <phase>install</phase>
                        <goals>
                            <goal>zipalign</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>android-deploy</id>
                        <phase>install</phase>
                        <goals><goal>deploy</goal></goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jarsigner-plugin</artifactId>
                <version>1.2</version>
                <executions>
                    <execution>
                        <id>signing</id>
                        <goals>
                            <goal>sign</goal>
                        </goals>
                        <phase>package</phase>
                        <inherited>true</inherited>
                        <configuration>
                            <archiveDirectory></archiveDirectory>
                            <includes>
                                <include>target/*.apk</include>
                            </includes>
                            <keystore>android.keystore</keystore>
                            <storepass>{{YOUR_PASSWORD}}</storepass>
                            <keypass>{{YOUR_PASSWORD}}</keypass>
                            <alias>android</alias>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

Now when you redeploy (mvn clean install) it will also create a target\YOURAPP-1.0-SNAPSHOT-aligned.apk...  That will be the one we upload to Google.

Make sure you are registered with the Android Market.

Click on the "Upload Application".



Fill in the form...  Here are a few notes/gotchas:

1. If your app's icon is not a 48x48 PNG, it will give you these error messages:
The icon for your application is not valid. Please use a 48x48 PNG.
Market requires versionCode to be set to a positive 32-bit integer in AndroidManifest.xml.
Market requires versionName to be set in AndroidManifest.xml.
Market requires the minSdkVersion to be set to a positive 32-bit integer in AndroidManifest.xml.
Fixing the icon resolves all of these.

2. You can't just browse and hit publish.  You have to hit "Upload" on each item.  The APK has to be uploaded before you can upload pictures.  Make sure to choose the -aligned.apk one.

3. Screenshots vs Promo graphics... Screenshots are the ones that show up on the app description page.  Promos are the tiny ones that scroll across the top (ie: Featured Apps).  Note the commentary in the side.  If you take a picture from the emulator, strip off the skin and resize it as suggested.

4. You can NOT have just 1 screenshot.  You can have 0.  You can have 2. You can not have 1.  Weird, I know.


If you still have your app installed on your phone for testing, uninstall it now.

And for a little fluff....
Go to the QR-Code Generator and specify a market url using the package name (not class name) of your app.  For example, for the Prime Browser, I used 'market://details?id=org.eoti.android.primebrowser'.  Choose whatever size you want (I did small on mine) and generate a QR Code... as such:

Once you have the QR code, you can put it on your blog (as I did on the left side of this page) or email it, or whatever you like.

The real key is that now you can pull out your phone, scan it with your Barcode Scanner and whala - you can see your app in the market place.

Go ahead and install it.  Enjoy =)