Choosing different libraries for compilation and runtime - android-studio

I have two libraries with same classes defined in each one. However they have some different contents (methods/constants).
For example:
Library 1:
package com.test.package;
Class A {
// only method signatures
public void methodA() {
}
public void methodB() {
}
}
Library 2:
package com.test.package;
Class A {
public void methodA() {
// some logic that MUST be executed to provide backward compatibility
}
}
My application uses Library 1 and Library 2 and run in devices which have com.test.package.ClassA, but com.test.package.ClassA.methodB() will only exist in newer releases in framework. Said that, I need the Library 1 to be used to compile my application and the Library 2 to execute a different implementation of methodA().
I have tried to do this in Android Studio using .jar and .aar libraries format, but none of them worked for me.
Is it possible to set this configuration in an Android Studio project?
I am building both Library 1 and 2, and I cannot add methodB() in Library 2.

For a simple Java application, you can do this by unlinking the compile and runtime configurations. I set up an example repository here.
The idea is shown in this commit, but can be described as manually resetting the runtime configuration so that it doesn't include the contents of the compile configuration. After doing so, you can just include your runtime library variation in the runtime configuration.
The application's build.gradle becomes something like:
apply plugin: 'application'
mainClassName = 'my.package.MyAppClass'
configurations.runtime.extendsFrom = [] // Reset runtime configuration
dependencies {
compile 'my.group:my.artifact:2.0' // Library 1, with the new method
runtime 'my.group:my.artifact:1.0' // Library 2, without the method
}
For Android, this can be a little more complicated. The problem is that there's no runtime configuration for Android (because you don't execute it on your computer, unless you're using Robolectric or something similar).
I think there are a few workarounds you can probably use, but one initial suggestion would be to create a wrapper library that abstracts away the dependency on the other libraries. This wrapper library you can compile with the newest library version (Library 1, with the new method). You could then include the wrapper library in the Android app while setting it as a non-transitive dependency and including the other library version:
dependencies {
compile 'my.group:my.wrapped.artifact:0.1' {
transitive = false // Don't include dependencies of the wrapper
// i.e., don't include version 2.0 of the lib.
}
compile 'my.group:my.artifact:1.0'
}
This should work because by setting the dependency as non-transitive Gradle doesn't recursively include the dependencies of the wrapper library, so the version used to compile the wrapper isn't included (in theory) in the APK. You can therefore add the old version without causing a conflict.
An example is set up in the same repository, under the Android branch. Firstly, two Java libraries are created. Then an Android library is created to wrap around the compile-time library. An example activity is created to show how using the wrapper library uses the compile-time library. Then, the latest commit shows how the app is configured to use the wrapper library (which compiles with the newest library) but forces the old library to be included instead in the runtime.
Hope this helps =)

Related

Kotlin / Gradle DSL integration to manage dependency version variables

Kotlin / Gradle DSL integration to manage dependency version variables
My question is about the best way to manage dependency version variables in IntelliJ Idea / Android Studio using Gradle
Kotlin DSL
The secondary purpose is to have the IDE perform version upgrades upon clicking the suggestion to change to newer
version automatically instead of manually editing the versioning file entry.
the simple way
definition
build.gradle.kts
plugins {
application
kotlin("jvm") version "1.7.10"
}
dependencies {
implementation("io.ktor:ktor-server-core-jvm:2.1.1")
}
Comments
This is working but "clumsy" and does not comply to single source of truth (SSOT)
I have seen several ways that attempt having a SSOT for the dependency versions
by Project
Definition
gradle.properties
ktor_version = 2.1.0
kotlin_version = 1.7.10
build.gradle.kts
val ktor_version: String by project // NOTE the complaint Property name 'ktor_version' should not contain underscores
val kotlin_version: String by project
plugins {
application
kotlin("jvm") version "1.7.10" // NOTE you cannot replace this with the variable!
// 'val kotlin_version: String' can't be called in this context by implicit receiver. Use the explicit one if necessary
}
dependencies {
implementation("io.ktor:ktor-server-core-jvm:$ktor_version")
}
Comments
This does not update the suggested version numbers via "Project Structure/Suggestions" in AS/IJ
my inline NOTES indicate several issues with this method!
by buildSrc java Module
Definition
object Ktor {
private const val ktorVersion = "2.0.0"
const val core = "io.ktor:ktor-client-core:${ktorVersion}"
const val android = "io.ktor:ktor-client-android:${ktorVersion}"
}
implementation in build.gradle.kts
implementation(Ktor.android)
Comments
This does not update the suggested version numbers via "Project Structure/Suggestions" in AS/IJ, is seemed to
be totally de-coupled, and it is a nightmare to manually find and update the versions manually to the latest!
However: doing it by buildSrc java Module makes it much easier to re-use in other projects
by Gradle Version Catalogs
Definition
libs.versions.toml // in the gradle directory next to wrapper
[versions]
plugin-kotlin = "1.7.10"
ktor = "2.1.0"
[libraries]
plugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "plugin-kotlin" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
[bundles]
plugins = ["plugin-android", "plugin-kotlin", "plugin-kotlin-serialization", "plugin-sqldelight"]
implementation in build.gradle.kts(project)
buildscript {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
dependencies {
classpath(libs.bundles.plugins)
}
}
implementation in build.gradle.kts(shared)
sourceSets {
val commonMain by getting {
dependencies {
implementation(libs.ktor.client.core)
}
}
}
Comments
This is by far the favorite of mine, the reason is clear, it allows SSOT, and it allows grouping/bundling to make
"sets" that is more understandable later on
However!
This does not update the suggested version numbers via "Project Structure/Suggestions" in AS/IJ
it is a nightmare to manually find and update the versions manually to the latest!
Main question
Is there a better way of managing dependency versions?
Is there a way that works inside the IDE AS/IJ suggestions to update the versions correctly at the definition
location?

Calling C external library (.so) from android java

I am writing an example android app to demonstrate the use of our (university research) C/C++ library.
I know that, using jni, we can call C functions from java.
However, I have not found a step by step set of instructions for how to do this within Android Studio Artic Fox.
I have seen the need to write jni compatible C wrapper functions, but have not found how to do this (correctly formed function signatures) or where to put them.
In addition, what do I need to change in the project setup to correctly build the project (using gradle) ?
Note that I have to use directly the pre-built .so file and the public header file which defines the set of public C functions for the library.
There are plenty of examples which give partial outdated information, but still nothing comprehensive - or have I missed something ?
I put together a quick guide below, but I want to clarify how it all fits together first.
In an Android application, you can bind native methods to specially-named functions that are loaded from a native library.
These specially-named functions receive pointers to a JNIEnv struct to interact with the embedding Java application.
The native library is typically built using CMake. Any external dependencies (such as your prebuilt library) need to be made visible to CMake in its CMakeLists.txt. The weapon of choice here are IMPORTED libraries, which are exactly what you think they are.
the steps
First, create an Android project with Kotlin as language.
Right click the app at the top of the tree and select "Add C++ to module" to generate the necessary build stuff.
Change your MainActivity.kt file to be:
class MainActivity : AppCompatActivity() {
external fun doit();
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val nativeThread = Thread {
doit()
}
nativeThread.start()
}
}
You will get a build error stating "cannot resolve corresponding JNI function". If you select the quick fix for that, Android Studio will generate a .cpp file with the appropriate JNI wrapper code inside it.
The generated function will look like:
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_MainActivity_doit(JNIEnv *env, jobject thiz) {
// TODO: implement doit()
}
and above that will be instructions on how to load the native library from Kotlin. Copy that code to your MainActivity.kt.
Edit that .cpp file to do whatever you need to do with your native library (eg #include some files and call some functions).
Finally, edit app/src/main/cpp/CMakeLists.txt to point to your headers and precompiled library.

Is it possible to use classes compiled by Gradle buildSrc in main Groovy project?

Classes compiled by buildSrc/build.gradle are not resolved at runtime when they are used in main PROJECT classes.
My Groovy project structure looks like this:
-PROJECT
-buildSrc/
-build.gradle
-/src/main/groovy
- com.company.global.test.report
-src/test/groovy
-build.gradle
Is there something I can add to the top-level PROJECT/build.gradle to allow the classes compiled by it to use the classes compiled by buildSrc/build.gradle?
buildSrc is its own build (not project) that gets executed before the main build. Its sole purpose is to make some classes (plugins, tasks, regular classes) available to the build scripts of the main build. Hence you could call it a "meta-build".
Technically, it would be possible to add the compiled classes of buildSrc to the compile or runtime class path of a project in the main build, but I don't recommend to do it. There is very likely a better way to achieve your goals (but I don't know what those are).
Here is how to do it with Gradle 2.12:
In your_project/buildSrc/build.gradle
task sourcesJar(type: Jar, dependsOn: classes) {
classifier = 'sources'
from sourceSets.main.allSource
}
// Thanks to this, IDE like IntelliJ will provide you with "Navigate to sources"
artifacts {
archives sourcesJar
}
In your_project/build.gradle
ext.buildSrcJars = fileTree("$rootDir/buildSrc/build/libs") { include("*.jar") exclude("*sources.jar")}
// Works in every subproject
dependencies {
compile buildSrcJars
}

Not able to use variables defined in classes within groovy annotations

I am trying to port some code from the Dropwizard examples from java to groovy.
I see that within java, I can use the following code without any issues:
package com.example.helloworld;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
#Produces(MediaType.APPLICATION_JSON)
public class HelloWorldService{
}
However, with the groovy compiler ( both 1.8 and 2.0.6 ), the class fails to compile with a noClassFoundException around MediaType.APPLICATION_JSON
If I change this code to use the actual string value
#Produces('application/json')
public class HelloWorldService{
}
everything works perfectly.
Are there any differences between the way groovy resolves annotations and the way that java does?
For completeness, this is part of a gradle project and here is my build.gradle ( the file goes under src/groovy/com/example/helloworld )
apply plugin: 'groovy'
// Set our project variables
project.ext {
dropwizardVersion = '0.6.1'
}
repositories {
mavenCentral()
}
dependencies {
compile group: 'com.yammer.dropwizard', name: 'dropwizard-core', version: dropwizardVersion
groovy group: 'org.codehaus.groovy', name: 'groovy-all', version: '1.8.7'
}
The compilation error is:
Caused by: java.lang.RuntimeException:
java.lang.ClassNotFoundException:
com.sun.ws.rs.ext.RuntimeDelegateImpl ... 17 more Caused by:
java.lang.ClassNotFoundException:
com.sun.ws.rs.ext.RuntimeDelegateImpl at
org.gradle.api.internal.tasks.compile.TransformingClassLoader.findClass(TransformingClassLoader.java:47)
The problem is caused by an unfortunate limitation of the Groovy compiler, namely that it uses reflection to access classes on the compile class path. This may in turn trigger other classes to get loaded, which may not be available on the compile class path. Typically (but not always) these are runtime dependencies.
In the concrete case, the Groovy compiler loads javax.ws.rs.core.MediaType via reflection, which ultimately results in com.sun.ws.rs.ext.RuntimeDelegateImpl being loaded via Class.forName (triggered by a static initializer), which isn't on the compile class path. The solution is to put that class on the compile class path. (In the longer run, the solution is to fix the standalone Groovy compiler not to use reflection, and from what I know this is already in the queue.) If your module's transitive dependencies aren't an issue, the simplest way to achieve this is:
dependencies {
compile "com.sun.jersey:jersey-client:1.15"
}
I suspect that the Eclipse Groovy compiler doesn't have this problem because it doesn't use reflection to access the compile class path. I'd expect GMaven to blow up like Gradle, unless it is configured to use the Eclipse compiler (which isn't currently supported by Gradle).

Can you force MonoTouch to include an unreferenced assembly in its static compilation?

I have a MonoTouch app that dynamically instantiates a class (using Type.GetType()) at runtime. The class is in an assembly that is not referenced anywhere else in the app, so the MonoTouch static compiler thinks that the assembly isn't used and ignores the assembly when it compiles the app. If I add a reference to the class in the app, then the compiler includes the assembly and the call to Type.GetType() works fine:
MyAssembly a;
I would prefer to just tell the compiler to always include all the assemblies listed in the project's "References" when it compiles the app. Is this possible?
Thanks,
-Tom B.
You will have to change your project's Linker behavior from "Link all assemblies" to "Link SDK assemblies only".
The other solution, if you have the project code that assembly was created with, is to mark the class you want to use with the PreserveAttribute.
Were you able to figure this out yet? If not, I had a similar problem: Is there a way to force MonoDevelop to build/load an assembly?
As I understand it, that's just how the C# compiler works. I was able to get around this by adding a custom pre-build step that scripts a class into the referencing assembly that includes dummy references to the unreferenced assemblies, like so:
using System;
namespace MyNamespace
{
public static class Referencer
{
Type t;
//These lines are scripted one per class in the unreferenced assemblies
//You should only need one per assembly, but I don't think more hurts.
t = typeof(Namespace1.Class1);
t = typeof(Namespace2.Class2);
...
t = typeof(NamespaceN.ClassN);
}
}

Resources