Sunday, October 4, 2009

Developing Android With Multiple Eclipse Projects

The Android application I'm developing at home has non-Android-specific schema, database access, and business logic, so it makes sense to partition the application into multiple Eclipse projects, some Android-specific and others not.

One benefit of using separate projects is that I can write a second version of the application that runs on a website instead of on a handheld without having to rewrite the bulk of the code.

But there's a catch: when the Android-specific unit tests are moved into a separate Eclipse project, the loader reports "Class resolved by unexpected DEX error" and the unit tests fail with "java.lang.IllegalAccessError: cross-loader access from pre-verified class". (In 2.1 the error remains, but the message has changed to "Class ref in pre-verified class resolved to unexpected implementation".)

I first ran into this problem in 1.5 Android, and upgraded to 1.6 hoping it would go away, because in 1.6 using separate Eclipse projects for unit tests is recommended:

http://mobilebytes.wordpress.com/2009/09/19/new-eclipse-android-test-features


http://www.danielswisher.com/2009/06/as-new-android-developer-i-have-been.html

There is even a toolbar icon and wizard to create an Android test project that points to a separate Android project to be tested.

Googling around, I found other programmers having the same or similar problems:

http://groups.google.com/group/android-platform/browse_thread/thread/20ff41b925e04dd4

But googling also found programmers with multiple projects but not the problem:

http://www.anddev.org/unit_testing_private_methods-t7847.html

What makes some programmers have no problem using multiple Eclipse projects to separate Android application code from Android unit-test code, while other programmers get the loader errors?

The problem shows up when the loader sees the same class loaded more than once:

http://groups.google.com/group/android-developers/browse_thread/thread/3440dd8e11a1b481

which can happen if the main Android code and the Android unit tests share code from another Eclipse project:

http://groups.google.com/group/android-developers/browse_thread/thread/5537ae10e4143240

which is the setup I have.

To reproduce the problem:
  1. Create an ordinary Java project called LoaderProblemCommon, and create this class in it:
  2. package com.loaderproblem;

    public class LoaderLogger
    {
    public static void log(String message)
    {
    System.out.println(message);
    }
    }
  3. Create an Android project called LoaderProblem, add the LoaderProblemCommon project to its build path, and create this class in it:
  4. package com.loaderproblem;

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

    public class Hello extends Activity
    {
    public void onCreate(Bundle savedInstanceState)
    {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    logHello();
    }

    public static void logHello()
    {
    LoaderLogger.log("Hello");
    }
    }
  5. Add LoaderProblem to the Android manifest.
  6. Create an ordinary Java project called LoaderProblemTestUtils, and create this class in it:
  7. package com.loaderproblem.testutils;

    public class TestUtil
    {
    public static final void doNothing()
    {
    }
    }
  8. Create an Android test project called LoaderProblemTests, add the other three projects to its build path, and create this class in it:
  9. package com.loaderproblem.tests;

    import android.test.AndroidTestCase;

    import com.loaderproblem.Hello;
    import com.loaderproblem.LoaderLogger;
    import com.loaderproblem.testutils.TestUtil;

    public class LogHelloTest extends AndroidTestCase
    {
    public void testLogHello()
    {
    LoaderLogger.log("Hello");
    Hello.logHello();
    TestUtil.doNothing();
    }
    }
The result should look like this:

Compile all four projects, create a brand-new AVD, and run LoaderProblem as an Android application. It should deploy and display:

Now try to run LoaderProblemTests as an Android JUnit test. It will fail with:


Or, for 2.1, it will fail with:

I first tried to fix this problem by editing the build path for LoaderProblemTests so it only depends on the LoaderProblemTestUtils Eclipse project, and then adding back the necessary classes by adding dependencies on the other projects' class folders:



This "worked", but it introduced several unwanted side effects:
  • Now that the dependent project's source is seen as a class folder, one can no longer smoothly refactor across projects or navigate into classes (via F3, for instance), because these files are now treated as a compiled objects. Attaching the source to the classfolder library sort of works, but then it brings up an uneditable version of the source. [reported by a reader]
  • It screws up EclEmma. For details see https://sourceforge.net/tracker/?func=detail&atid=883351&aid=2934081&group_id=177969.
The proper way to fix this problem was reported by another reader:
  1. In the build path for LoaderProblem, click on "Order and Export".
  2. Check the box for LoaderCommon, to let LoaderProblem export the LoaderCommon classes as well as its own.
  3. In the build path for LoaderProblemTests, remove the dependency on the LoaderCommon project. The dependency is already now covered by the LoaderProblem dependency.
  4. Clean, build, rerun LoaderProblem, then rerun LoaderProblemTests.
This solution also fixes the EclEmma problem.

23 comments:

  1. Thanks for the link this will help in one of the book proposals/books I am pitching to Apress.

    ReplyDelete
  2. You're welcome! Good luck with the book.

    ReplyDelete
  3. I tried this same route but found a downside: now that the dependent project's source is seen as a class folder, one can now longer smoothly refactor across projects or navigate into classes (via F3, for instance), since these files are now treated as a compiled objects. The best I could find is to also attach the source to the classfoler library, but then it brings up an uneditable version of the source.

    Is this your experience as well? Ideally, the plugin would support a means to exclude packages/projects from being part of the resulting apk, but as far as I can see, this is not yet doable via the plugin and one apparently needs to use Ant instead.

    ReplyDelete
  4. Yes, that is my experience as well--specifying only class-library dependencies on other projects breaks navigation and refactoring.

    The hack described above is the best of a bad set of options, but it is definitely suboptimal. I don't think the Android team put much emphasis on common utilities, separate test projects, layered subsystems, etc.

    These problems have been reported to the Android team, and over time I hope they will be addressed.

    ReplyDelete
  5. Thanks, I struggled to find a solution for this and it was just under my nose.

    ReplyDelete
  6. I ran into another problem caused by the dependency on another project's class library: it screws up EclEmma. For details see https://sourceforge.net/tracker/?func=detail&atid=883351&aid=2934081&group_id=177969.

    ReplyDelete
  7. I had a similar problem and with the inspiration of this article I found another solution. One that I feel more "correct". Perhaps it works for you as well.

    Start with the unmodified problem setup as described in the article.

    The solution might be to go into the Build path setting of LoaderProblem project and enter "Order and Export". Here you tick the box for LoaderCommon, to let LoaderProblem export the LoaderCommon classes as well.

    Then go into LoaderProblemTests and remove the Build path dependency to the LoaderCommon project. The dependency should be covered by the LoaderProblem dependency.

    Clean, build and test. Did it work for you too?

    ReplyDelete
  8. Yes, that worked! I updated the information above to use your fix instead of my hack.

    ReplyDelete
  9. What if the shared project is an Android project? That is, what if you want to share Android code with several projects and then test one of those projects?

    See http://groups.google.com/group/android-developers/browse_thread/thread/42d9683075d0eeff

    ReplyDelete
  10. That's a different use case and I haven't tried it. Also I'm not creating JARs, just classes, so the problem reported on that link (http://groups.google.com/group/android-developers/browse_thread/thread/42d9683075d0eeff) is different. That link refers to another link (http://groups.google.com/group/android-developers/browse_thread/thread/5537ae10e4143240) that describes a solution that is similar to the one here, except they add a dependency on the Android JAR from the shared non-Android project.

    ReplyDelete
  11. Google has addressed the problem, at least partially: http://developer.android.com/guide/developing/eclipse-adt.html#libraryProject.

    ReplyDelete
  12. (The reason I said "partially" is because their approach seems to have several drawbacks/limitations: http://groups.google.com/group/android-developers/browse_thread/thread/0b4a5d751346655b/a158ce21aa1b971a?show_docid=a158ce21aa1b971a.)

    ReplyDelete
  13. If someone's experiencing this issue in a Maven-based Android build, the secret is to change the test APK's dependency on the application APK to be 'provided' scope to match the test's dependency on the application JAR.

    Otherwise any library depended on by the application APK as a 'compiled' scope will be packaged into the test APK, causing this error.

    ReplyDelete
  14. I have this problem with an ant build script. I have 2 LIBS, A and B. One Android project C and one test project T. The dependencies are: C -> (A, B) and T -> (A, C). However compiling works. Running in eclipse works, but when i "ant release" it i get this problem.... ugh. Anybody knows how to solve it with ant. I use SDK 4.0 (r15)

    ReplyDelete
  15. This comment has been removed by the author.

    ReplyDelete
  16. Workaround is finding an autosynch application and synch source folders as needed. Tried SynchToy from Micro$oft, works but requires you to click each time.

    ReplyDelete
  17. Nice article. Think so new form of features have included in your article. Waiting for your next article.

    _________________________________________
    App development company

    ReplyDelete
  18. Great article post, I really interesting the way you highlighted some important points. Thanks very much, I appreciate your post.


    ______________________
    Total video downloader for Mac

    ReplyDelete
  19. Very efficiently written information. It will be valuable to everyone who uses it, including myself. Thanks a lot!

    _____________________________
    Banners new orleans

    ReplyDelete
  20. This is very interesting to read such an amazing articles. Whole blog was really an awesome site which I have never found anywhere. International freight software

    ReplyDelete
  21. I am thankful to you because your article is very helpful for me to carry on with my research in same area. Your quoted examples are very much relevant to my research field.

    Curso java

    ReplyDelete
  22. A number of viewers are keen to watch comic video clips, but I like to watch terrible videos on YouTube.

    --------------------------
    Software development

    ReplyDelete
  23. Decent replies consequently of this inquiry with genuine contentions and clarifying about that.
    Web Application Development Singapore

    ReplyDelete