1

I have decided to ask the question after my research did not reveal any useful solutions for my scenario which I believe should be quite popular.

My question is slightly related to this SO question, but the solution did not help in my scenario.

I want to create a Java module that will contain only my application logic. That said, I do not want it to be an Android Library module. I do not need to have manifest files and others for such a library. There will not be any Android activities in my application logic Java module. All Android-specific code will be in the Android module (called app). My Java module is called applogic.

So technically, I want my project to look like

AppProjectName
|-->app //Only Android-specific code here
   |
   -->src
|-->applogic //General, reusable Java-code here
   |
   -->src

In the app module settings, I add applogic as a module dependency. From my Android app, I create objects defined in classes in applogic. I am able to do that easily.

And here is the thing. I want to use Android's logging capabilities (in android.util.Log). How can I use that for my Java library?

A possible solution that was stated in the SO question mentioned above was to add android.jar as a library to my applogic module. I was not able to do that in Android Studio. Any guidance will be greatly helpful.

Thanks!

Community
  • 1
  • 1
Shailen
  • 7,909
  • 3
  • 29
  • 37
  • 1
    "I do not want it to be an Android Library module" -- why? "I do not need to have manifest files and others for such a library" -- so? "There will not be any Android activities in my application logic Java module" -- so? If you had just used an Android library module, and paid the price of a nearly-empty manifest file, you would have been done by now. – CommonsWare Aug 23 '15 at 15:29
  • 1
    @CommonsWare Good points, but the *applogic* Java module needs to be portable. I may plan to use it elsewhere. I will have flags around my Android-specific code, like `Log.v()`. If I want my library to be just a Java library, there must be a way to be able to use some Android APIs by including `android.jar`. What do you think? – Shailen Aug 23 '15 at 15:35
  • "but the applogic Java module needs to be portable" -- so? Create a JAR out of the Android library module. – CommonsWare Aug 23 '15 at 15:37
  • I gave your suggestion a try. I created an Android Library called *myapplib*. This needs to be a dependency for the Android *app* module. From the Module settings, I am not able to add *myapplib* as a dependency to *app*. The *myapplib* is a work in progress. If you ever encountered the same scenario as I did, how did you go about solving it? Thanks! – Shailen Aug 23 '15 at 15:51
  • "From the Module settings, I am not able to add myapplib as a dependency to app" -- what are your specific symptoms? I usually just set it up by hand, having `settings.gradle` list both modules, and adding `compile project(':myapplib')` in my app's `build.gradle`. – CommonsWare Aug 23 '15 at 15:53
  • "If you ever encountered the same scenario as I did, how did you go about solving it?" -- I would not assume that code developed using the Android SDK will work on any version of the JVM. If I wanted a portable JAR, I would create a portable JAR compiled against a JVM. For anything where I wanted to do Android-specific stuff related to the library, I would use interfaces and such to allow the hosting code (e.g., the Android app) to supply implementations that do the Android-specific stuff (e.g., logging to LogCat), so the library is Android-free. – CommonsWare Aug 23 '15 at 15:55

4 Answers4

5

If you're running within the context of a project created by Android Studio, you'll have a local.properties file giving the SDK location. Even if you're haven't got such an auto-generated file, there's nothing stopping you from creating it. It looks something like this:

## This file is automatically generated by Android Studio.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Tue Apr 04 12:34:41 BST 2017
ndk.dir=C\:\\Users\\smith\\Programs\\android-sdk\\ndk-bundle
sdk.dir=C\:\\Users\\smith\\Programs\\android-sdk

Either way, you can then use it in what I assume is the same manner as the Android Gradle plugin does:

dependencies {
    Properties localProps = new Properties()
    localProps.load(project.rootProject.file('local.properties').newDataInputStream())
    implementation files("${localProps.getProperty('sdk.dir')}/platforms/android-15/android.jar")
}

Of course, you'll have to make sure you've downloaded the appropriate version of the platform using the Android SDK Manager, but if you're an Android developer you've probably already done that.

mhsmith
  • 6,675
  • 3
  • 41
  • 58
1

I have the same setup for one of my projects. I'm using android.util.Log and android.os.Build classes in a Java Library module.

In your app module, you should add applogic as a module dependency to build.gradle, which you have already done.

Then, in your build.gradle for applogic module, you should add android.jar dependency like this:

apply plugin: 'java'

dependencies {
    compile files('libs/android.jar')
}

Since you are using android.util.Log which is available since API 1, then you can use any "android.jar" from any SDK Platform. Just copy it to your applogic module libs folder. Android Studio creates libs folder for Java Library modules.

So, your final project should look like

AppProjectName
|-->app
   |
   -->src
|-->applogic
   |
   -->src
   -->libs
     |
     -->android.jar
Karim ElDeeb
  • 375
  • 2
  • 7
  • Karim, thank you for this response. Let me give this a try and come back. Actually, is there any other way of doing that without copying the jar file? If my SDK manager updates the SDK, I will have to manually maintain the .jar files. – Shailen Sep 03 '15 at 12:58
  • Well, your _applogic_ module is no different than any other module. Resolving its dependency on other libraries can be done by either copying the libraries to _libs_ folder, or letting your build system grab them from an online repository. The problem with having a dependency on `android.jar` however, is that the file is not available in any repository. That's why you must copy it yourself from the SDK platform folder. – Karim ElDeeb Sep 04 '15 at 03:59
  • You don't need to maintain/update `android.jar` whenever the SDK Manager updates the SDK. You simply use any `android.jar` file that supports the APIs you want to call from your module. Since you need to call `android.util.Log` (available since API 1) then you can use any `android.jar` file. If, however, you want to use `android.util.Base64` class (available since API 8) then you must copy `android.jar` file from Android 2.2/API 8, or higher, since the class doesn't exists before API 8. Once you copied this file, you no longer need to change it. – Karim ElDeeb Sep 04 '15 at 04:01
  • I believe this is how Android Studio is doing it, since each module is built separately. When you create an Android Library module and use android-specific APIs, then `android.jar` will be compiled with your module to resolve all dependencies. But when you create a Java Library module, Android Studio does not include `android.jar` automatically. – Karim ElDeeb Sep 04 '15 at 04:09
0

There is another way to solve the problem w/o using 'android.jar', but it is very round-about.

  1. Create an usual jar module (for example with name 'dummydroid') and add empty implementation of required android API:

    // file dummydroid/src/main/android/util/Log.java
    package android.util;
    public class Log {
      public static int e(String tag, String msg, Throwable tr) { return 0; }
      //... etc
    }
    
  2. add the 'dummydroid' module as a compileOnly dependency to the applogic module

    // applogic/build.gradle
    ...
    dependencies {
        ...
        compileOnly project(path: ':dummydroid')
    }
    
  3. That is all. The dummydroid object helps to resolve calls to android API during compilation of the applogic module. Because of the compileOnly type the dummydroid.jar is not transitively included in the compilation of the android app module --- and API references in applogic.jar are resolved to calls into the real android.jar on device/emulator.

The big shortcoming of this idea is that you have to make dummy implementation of all API those are used in the applogic.

lazyden
  • 456
  • 5
  • 10
0

Write an interface that mimics the android Log interface

public interface LogInterface {
    int ASSERT = 7;
    int ERROR = 6;
    int WARN = 5;
    int INFO = 4;
    int DEBUG = 3;
    int VERBOSE = 2;
    int d(String tag, String msg, Throwable tr)
    int d(String tag, String msg)
    int e(String tag, String msg)
    int e(String tag, String msg, Throwable tr)
    String getStackTraceString(Throwable tr)
    ...

Modify your main activity so that it implements LogInterface and add the implementation methods for the LogInterface

class MainActivity : AppCompatActivity() implements LogInterface {

public d(d(String tag, String msg, Throwable tr) {
    return Log.d(tag,msg,tr);
}

public d(d(String tag, String msg) {
    return Log.d(tag,msg);
}
public e(d(String tag, String msg, Throwable tr) {
    return Log.e(tag,msg,tr);
}

public e(d(String tag, String msg) {
    return Log.e(tag,msg);
}

public String getStackTraceString(Throwable tr) {
      return Log.getStackTraceString(tr)
}
....

Write your own Log accessor class probably storing it in its own java library module:

final public class Log {

    private Log() {} // prevent this class from being instantiated

    static private LogInterface logInterface;
      static protected void setLogInterface(LogInterface implementation) {
        logInterface = implementation;
    }

    static public int ASSERT = LogInterface.ASSERT;
    static public int ERROR = LogInterface.ERROR;
    static public int WARN = LogInterface.WARN;
    static public int INFO = LogInterface.INFO;
    static public int DEBUG = LogInterface.DEBUG;
    static public int VERBOSE = LogInterface.VERBOSE;
    static d(String tag, String msg, Throwable tr) {
        logInterface.d(tag,msg,tr);
    }
    ...

Initialize the static class in your MainActivity's onCreate method

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    Log.setLogInterface(this)
    ...
}

Include the module containing Log class as a dependencies in your java library. Then in your java library you can use a statement like:

 Log.d("myJavaLibrary", "here's a debug message");

lazyDen's solution is fairly clever, I think it works and it involves less core writing. I don't however like the concept of calling android methods directly from inside a Java library. The android.jar is code that is written to run on top of a properly configured android environment. When you start using android methods from inside your java code, you have no guarantees that your android environment is properly setup when you use methods. Probably is, but who knows for sure. In using the approach that I suggested you don't have to worry whether this is a issue or not. I would strongly suggest avoiding Karim's suggestion of adding the android.jar as a dependency to the java library. That seems like asking for problems.

Tom Rutchik
  • 1,183
  • 1
  • 12
  • 15