How to run JavaFX on Android - android

I try to run a simple JavaFX application on Android. Therefore I read the javafxports Getting Started Guide using gradle to build the app, but I got stuck somewhere. I'm able to build and install the app on an Android device using the installDebug task of gradle within Eclipse, however, when I start the app I get a black screen. When I extract the .apk file it does not contain the jar file of the JavaFX application. I assume the apk should contain the JavaFX application jar-file, but I have no idea how to include it into the .apk.
I also tried to use gradle to build the JavaFX application itself (jar-file), which works fine. However, I don't know where to put this jar-file that it can be included into the apk-file. I read I've to put it into a dist directory, but I assume this would only be used in Netbeans right? I'm using the Eclipse gradle integration to build the project.
Here is what I tried. Since the JavaFX application is as simple as the HelloWorld sample app and works like a charm, I expect a missconfiguration within my gradle build file.
build.gradle - to build the apk
apply plugin: 'me.tatarka.retrolambda'
apply plugin: 'android'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'me.tatarka:gradle-retrolambda:2.5.0'
classpath 'com.android.tools.build:gradle:1.0.1'
}
}
repositories {
jcenter()
}
ext {
dalvikSdkHome = getProjectProperty('dalvikSDK')
dalvikSdkLib = dalvikSdkHome + '/rt/lib'
}
android {
compileSdkVersion 21
buildToolsVersion "21.1.1"
dexOptions {
preDexLibraries = false
}
sourceSets {
main {
jniLibs.srcDir file("${dalvikSdkLib}/")
assets.srcDirs = ['assets']
}
}
lintOptions {
abortOnError false
}
}
dependencies {
compile files ("${dalvikSdkLib}/ext/jfxrt.jar",
"${dalvikSdkLib}/ext/jfxdvk.jar",
"${dalvikSdkLib}/ext/compat-1.0.0.jar")
}
project.tasks.withType(com.android.build.gradle.tasks.Dex) {
additionalParameters=['--core-library']
}
String getProjectProperty(String propertyName) {
project.hasProperty(propertyName) ? project.property(propertyName) : null
}
build.gradle - to build the jar
// Declares binary plugin and its required JavaFX classpath
apply from: "http://dl.bintray.com/content/shemnon/javafx-gradle/0.4.0/javafx.plugin"
// Configures plugin
javafx {
// Points to JDK and its JavaFX libraries, also declares target runtime JDK
javaRuntime = getProjectProperty('javaJDKPath')
// Application name and ID presented by target OS
appID 'HelloWorldApp'
appName 'Hello World Application'
// Main class of application
mainClass 'helloworld.HelloWorld'
// JVM arguments, system properties, application command line arguments
jvmArgs = ['-XX:+AggressiveOpts', '-XX:CompileThreshold=1']
systemProperties = ['prism.disableRegionCaching':'true']
arguments = ['-l', '--fast']
// Keystore credentials for signing JAR
// Generate key: keytool -genkey -alias release -keyalg RSA -keystore keystore.jks -keysize 2048
releaseKey {
alias = 'release'
keyStore = file(getProjectProperty('keystoreJKSFile')) // keyStore = file("${System.properties['user.home']}/keystore/keystore.jks")
keyPass = getProjectProperty('keyStorePassword')
storePass = getProjectProperty('storePassword')
}
signingMode 'release'
// ...
}
String getProjectProperty(String propertyName) {
project.hasProperty(propertyName) ? project.property(propertyName) : null
}
gradle.properties
javaJDKPath=D:/Java/jdk1.8.0_20
dalvikSDK=D:/Java/dalvik-sdk-8u20b3/dalvik-sdk
keystoreJKSFile=D:/Java/jre1.8.0_20/bin/keystore.jks
keyStorePassword=password
storePassword=password
local.properties
sdk.dir=D:/programme/Android/adt-bundle-windows-x86_64-20130917/sdk
And this is my project structure
HelloWorld
-- src\main
-- java\helloworld\HelloWorld.java
-- res\
-- AndroidManifest.xml
-- assets\
-- javafx.platform.properties
-- javafx.properties
-- build.gradle
-- gradle.properties
-- local.properties
Do I need to use a dist directory? Where would I put the jar-file of my JavaFX application that it will be included into the apk-file?

Now answering my own question.
The configuration of the gradle setup, as posted in my question, is already running. In my question, I did not show you that I was using a .fxml file for the layout. So the reason why I did not get it running was because the .fxml layout has not been placed at the right spot to be packaged with the apk file (LogCat showed the error Location Not Found and I had a black screen on my device).
First of all, here is a working sample for the HelloWorld.java (see structure and gradle setup etc. in my question):
package helloworld;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class HelloWorld extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World!");
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
}
}
Run JavaFX on Android using fxml layouts
If you would like to use .fxml files, you need to change the structure of your project a little bit. All .fxml files, .css files, graphics etc. belong into the resources\assets directory or subdirectory. This ensures that the .fxml files etc. will be packaged in the apk.
HelloWorld
-- src\main
-- java\helloworld\
-- HelloWorld.java
-- MyController.java
-- resources\assets\
-- sample_layout.fxml
-- AndroidManifest.xml
-- assets\
-- javafx.platform.properties
-- javafx.properties
-- build.gradle
-- gradle.properties
-- local.properties
I did not check, if the assets folder which contains the javafx.platform.properties and the javafx.properties (both from the dalvikVM sdk) is still required. If I check the content of the .apk file, the apk contains both files twice. Seems like the dalvikVM library copies these files automatically.
Note: If you need to check the content of your apk, extract the apk file, then extract the classes.dex which is part of the apk (see this post for more details)
Here is an example using .fxml files:
HelloWorld.java
package helloworld;
import java.io.IOException;
import java.net.URL;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
public class HelloWorld extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
try {
URL fxmlFile = getClass().getResource("/assets/sample_layout.fxml");
FXMLLoader loader = new FXMLLoader(fxmlFile);
AnchorPane page = (AnchorPane) loader.load();
MyController controller = (MyController) loader.getController();
Scene scene = new Scene(page);
primaryStage.setScene(scene);
primaryStage.setTitle("JavaFX Sample");
primaryStage.show();
} catch(IOException e) {
e.printStackTrace();
}
}
}
MyController.java
package helloworld;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
public class MyController {
#FXML
private ResourceBundle resources;
#FXML
private URL location;
#FXML
private Label label_Counter;
#FXML
private Button button_IncrementCounter;
#FXML
private Button button_DecrementCounter;
private static final String OUTPUT_PREFIX = "Counter: ";
private static int counter = 0;
#FXML
void onIncrementButtonPressed(ActionEvent event) {
label_Counter.setText(OUTPUT_PREFIX + ++counter);
}
#FXML
void onDecrementButtonPressed(ActionEvent event) {
label_Counter.setText(OUTPUT_PREFIX + --counter);
}
#FXML
void initialize() {
assert label_Counter != null : "fx:id=\"label_Counter\" was not injected: check your FXML file 'sample_layout.fxml'.";
assert button_IncrementCounter != null : "fx:id=\"button_IncrementCounter\" was not injected: check your FXML file 'sample_layout.fxml'.";
assert button_DecrementCounter != null : "fx:id=\"button_DecrementCounter\" was not injected: check your FXML file 'sample_layout.fxml'.";
label_Counter.setText(OUTPUT_PREFIX + 0);
}
}
sample_layout.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="helloworld.MyController">
<children>
<VBox layoutX="332.0" layoutY="71.0" prefHeight="400.0" prefWidth="600.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<Label text="Please click on the buttons to increment or decrement the counter:" />
<Button fx:id="button_IncrementCounter" mnemonicParsing="false" onAction="#onIncrementButtonPressed" text="Increment Counter">
<VBox.margin>
<Insets top="10.0" />
</VBox.margin>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</Button>
<Button fx:id="button_DecrementCounter" mnemonicParsing="false" onAction="#onDecrementButtonPressed" text="Decrement Counter">
<VBox.margin>
<Insets top="10.0" />
</VBox.margin>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</Button>
<Label fx:id="label_Counter" text="<output-placeholder>">
<VBox.margin>
<Insets top="20.0" />
</VBox.margin>
</Label>
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</VBox>
</children>
</AnchorPane>
Hope that helps everyone else getting started with JavaFX on Android.

How to run javafx on android:
Download dalvik sdk.
Go to samples\HelloWorld\javafx - it's gradle project
Modify location of dalvik-sdk and android-sdk in local.properties
Example on my Windows system:
sdk.dir=C\:\\dev\\android-sdk
javafx.dir=C\:\\dev\\dalvik-sdk
Run gradlew installDebug for building and installing apk on device. You also find outputs in build folder
Launch application on device.(I seen black screen over 10 sec before first launch)
Open project in Eclipse or Idea as standart gradle project

Related

Cannot find symbol dependencyResolutionManagement in GradleSettingsModel

I am trying to write a plugin that adds my repo to settings.gradle if available or in build.gradle in other cases, like this:
public static void addLibraryRepositories(GradleBuildModel file, GradleSettingsModel settings, String url) {
// Check if settings.gradle contains the repositories
RepositoriesModel repositoryModel = null;
if (settings != null) {
repositoryModel = settings.dependencyResolutionManagement().repositories();
}
// if not use the build.gradle file
if (repositoryModel == null || repositoryModel.repositories().size() <= 0) {
repositoryModel = file.repositories();
}
// add my maven repo
if (!repositoryModel.containsMavenRepositoryByUrl(url)) {
repositoryModel.addMavenRepositoryByUrl(url, "My Repo");
file.applyChanges();
}
}
But I am getting this error when running gradle task 'buildPlugin':
error: cannot find symbol repositoryModel = settings.dependencyResolutionManagement().repositories();
^
symbol: method dependencyResolutionManagement()
location: variable settings of type GradleSettingsModel
Using IntelliJ 2022.1.3, there is no error on IDE, I can navigate to the definition of the interface and the GradleSettingsModelImpl implementation, in project view I can see it is included in android-gradle-dsl.jar.
I have these deps in plugin.xml
<depends>com.intellij.modules.all</depends>
<depends>com.intellij.modules.java</depends>
<depends>org.jetbrains.android</depends>
<depends>org.intellij.groovy</depends>
I can call other methods of the same class without this error.
Any help is appreciated.
PS Already tried invalidating cache.

How to add third-party library reference to documentation

I've implemented dokka in top build.gradle
apply plugin: 'org.jetbrains.dokka-android'
dokka {
// These tasks will be used to determine source directories and classpath
// see https://github.com/Kotlin/dokka/blob/master/README.md
kotlinTasks {
defaultKotlinTasks() + [':core:compileDebugKotlin']
}
}
And in core module i have interface that want to be documented:
import com.f2prateek.rx.preferences2.RxSharedPreferences
/**
* Интерфейс для работы с [android.content.SharedPreferences] с помощью [RxSharedPreferences]
*
* #property prefs Свойство для доступа к [android.content.SharedPreferences] через [RxSharedPreferences]
* #see RxSharedPreferences
*/
interface FeaturePrefs {
val prefs: RxSharedPreferences
}
When i run dokka task i got warnings
Can't find node by signature `com.f2prateek.rx.preferences2.RxSharedPreferences`, referenced at my.package.FeaturePrefs (FeaturePrefs.kt:5)
Is there a way to use third party libraries in documentation? I'd like to configure it to reference to github source code (https://github.com/f2prateek/rx-preferences).
Already tried to configure it via "externalDocumentationLink", but no luck there, couldn't find any javadoc/package-list referenced to this lib.
If you click on android.content.SharedPreferences in documentation, you will be redirected to https://developer.android.com/reference/android/content/SharedPreferences.html, i want to achieve same behavior for rxprefs (redirect to github)
UPD: dokka configuration moved from root build.gradle to application's:
apply plugin: 'org.jetbrains.dokka-android'
dokka {
externalDocumentationLink {
url = new URL("http://reactivex.io/RxJava/javadoc/")
}
externalDocumentationLink {
url = new URL("http://jakewharton.github.io/timber/")
}
externalDocumentationLink {
url = new URL("https://dagger.dev/api/2.0/")
}
Set<ProjectDependency> deps =
project.configurations.collectMany {
it.allDependencies
}.findAll {
it instanceof ProjectDependency
}
sourceDirs = files(deps.collect {
p ->
def path = new File(p.getDependencyProject().projectDir, "/src/main/java")
return path
})
}
I've added some external documentation deps, but still getting a lot of warnings like
Can't find node by signature `javax.inject.Provider`
or
Can't find node by signature `androidx.fragment.app.Fragment`
Maybe it's impossible to have links on that kind of dependencies?
I'd like to have only my code to be documented, but have external links for framework's and third party's dependecies, is it real?

How to generate java files from swig?Is this done by gradle or do it with command line?

This is my Build.gradle file:-
project.ext {
// * SWIG options *
// This and the SWIG "task" at the bottom are loosely based on:
//
// http://androiddeveloper.co.il/using-swig/
swigModuleFiles = ['yourfy.i']
swigIncludeDirs = ['src/main/cpp/yourfy/src', 'src/main/cpp/yourfy/src/nxcommon/src/libnxcommon']
swigJavaOutputDir = file("src/main/java/com/yourfy/yourfy/swig").absolutePath
swigCxxOutputDir = file("src/main/cpp/swig").absolutePath
swigModuleFilesAbs = []
swigIncludeDirOpts = []
swigCxxModuleFilesAbs = []
swigModuleFiles.each { moduleFile ->
swigModuleFilesAbs.add(file("src/main/cpp/yourfy/src/yourfy/" + moduleFile).absolutePath)
swigCxxModuleFilesAbs.add(swigCxxOutputDir + "/" + moduleFile + ".cxx")
}
swigIncludeDirs.each { incDir ->
swigIncludeDirOpts.add("-I" + file(incDir).absolutePath)
}
}
// * Generate SWIG wrappers *
// Generate .java and .cxx files for the SWIG bindings.
//
// It has to be done in Gradle (as opposed to the native library's CMakeLists.txt), because
// Gradle calls the Java implementationr BEFORE running the native build, so the .java SWIG files will
// not have been generated yet when the implementationr runs. We have to ensure that they are generated
// before the Java implementationr runs.
//
// Thanks, Gradle!
//
// I would love to do this as a task, but unfortunately, it does not work. For some reason,
// when building from Android Studio (NOT when running Gradle from command line), CMake is
// actually invoked first, before any other tasks, which means that the Gradle-generated SWIG
// CXX source files might still be missing, which CMake then complains about and aborts. For
// now, we will have to run SWIG at the top level all the time to get it working. Right now,
// it's reasonably fast to do so, let's see how long this holds.
// More info:
def swigExec = '/usr/local/bin/swig'
// TODO: Add some auto-detection
if (project.hasProperty('swig.executable')) {
swigExec = project.property('swig.executable')
}
if (swigExec != null && file(swigExec).isFile()) {
file(project.swigJavaOutputDir).mkdirs()
file(project.swigCxxOutputDir).mkdirs()
// Delete previous output files (.cxx, .h and *.java in respective directories)
(file(project.swigJavaOutputDir).listFiles() + file(project.swigCxxOutputDir).listFiles()).each { file ->
if (file.name.toLowerCase().endsWith(".cxx")
|| file.name.toLowerCase().endsWith(".h")
|| file.name.toLowerCase().endsWith(".java")
) {
file.delete()
}
}
[project.swigModuleFilesAbs, project.swigCxxModuleFilesAbs].transpose().each { moduleFile, cxxFile ->
exec {
commandLine(
swigExec,
'-java',
'-c++',
'-package', 'com.yourfy.yourfy.swig',
*project.swigIncludeDirOpts,
'-outdir', project.swigJavaOutputDir,
'-o', cxxFile,
moduleFile
)
}
}
} else {
logger.error('Property swig.executable not set or invalid! You should set it in ' +
'the gradle.properties file of your gradle user home directory, pointing to ' +
'a SWIG > 3.0 executable')
}
How to generate swig wrapper files in android?There are some links added to here but still its not figure out.
Also tried following links also mentioned in the comment:-
Stackoverflow question link
Github link
I was trying both the links but didn't understand but still seems it difficult.

Android Studio Gradle sourceSets copy

Android Studio 2.0 Preview 2, Gradle Wrapper 2.8, Mac OS X
-MainProjectWorkspace
-|-build.gradle
-|-settings.gradle
-|-gradle.properties
-|-gradle
-|-MyLibraryDependencies
-|-MyMainModule
--|-build.gradle
--|-build
--|-src
---|-androidTest
---|-main
----|-assets
----|-jniLibs
----|-libs
----|-java
-----|-com
----|-res
----|-AndroidManifest.xml
MyMainModule/build.gradle
//Not a single SourceSets configurations.
productFlavors {
flavor1 {
}
flavor2 {
}
}
buildTypes {
release {
}
debug {
}
}
A genius developer left System.out.println statements, instead of Log statements in several hundreds of Java source-files in 'src/main/java'. Ideally, we do not want Sysout statements getting bundled with either of the applicationVariants, specially flavor1Release and flavor2Release.
Before we make amends to those hundreds of Java source-files and eventually switch the Sysout statements to Log statements, we would need to turn them off urgently.
Two possible solutions -
Execute a simple script that will remove all the Sysout statements in the Java source-files in 'src/main/java'. But about that, variants flavor1Debug and flavor2Debug need the Loggers displaying what's going on in the App.
Execute a simple Gradle task at build-time, copy Java source-files from 'src/main/java' to 'src/release/java', ensuring Sysout statements are omitted.
Solution 2 appears to be quickest, easiest, elegant. Particularly when the Gradle Custom-task is executed independently. Except for that, Gradle-sync in Android-Studio seems to be copying everything from 'src/main' to 'src/release' including assets, jniLibs, libs, res and even the AndroidManifest.xml. Fortunately, the Java source-files are ripped-off the Sysout statements in 'src/release', which is the expected result, and ideally, only 'src/release/java' should remain without any other folders.
task prepareRelease(type: Task) {
Pattern sysoutRegex = Pattern.compile(<Sysout-Pattern>)
try {
File releaseFolder = file("src/release")
File mainFolder = file("src/main")
ByteArrayOutputStream output = new ByteArrayOutputStream()
exec {
commandLine = "sh"
args = ["-c", "find ${mainFolder.canonicalPath} -name '*' -type f -print ",
"| xargs egrep -l '${sysoutRegex.pattern()}'"]
standardOutput = output
}
def fileList = output.toString().split(System.getProperty("line.separator"))
fileList.each { line ->
File srcFile = file(line)
File destFile = file(srcFile.canonicalPath.replace("main", "release"))
def content = srcFile.readLines()
if (!destFile.exists()) {
destFile.parentFile.mkdirs()
destFile.createNewFile()
destFile.writable = true
}
destFile.withWriter { out ->
content.eachWithIndex { contentLine, index ->
if (!sysoutRegex.matcher(contentLine).find()) {
out.println contentLine
}
}
}
} catch (Exception fail) {
throw fail
}
}
There is nothing in the custom Gradle-task that may cause this error of making "src/release" an exact copy of "src/main", which was not intended to be.
Any pointers to prevent this default copy of "src/main" to "src/release" will be greatly appreciated.
Based off RaGe's comment - "How about using *.java as your find pattern instead of * ?"
Kudos. I was not supposed to break the "find | xargs egrep " before the '|' into two separate args in the args[] in the exec task. Indeed, a Genius!!!

New Relic in Android Studio - newrelic.properties - variants

I'm integrating New Relic in my project (with Android Studio & Gradle) which has 2 variants. Each variant has its own generated token, which I store in each variant's string.xml file.
In the New Relic documentation, it states the following:
In your project’s root directory (projectname/app), add a newrelic.properties file with the following line:
com.newrelic.application_token=generated_token
The problem is, if I do this, how can make the correct token appear for the correct variant? If this file must appear in the project root, I can't create one per variant, and so I'm forced to use the same token for both variants, which doesn't work for my requirements.
Any insight would be appreciated.
Okay, so after contacting the support team at New Relic, there is apparently no direct solution for this as of today, although they said they've opened a feature request, and so this problem might be solved soon.
From what I managed to understand, the reason this file is needed is so that the New Relic system can display an un-obfuscated error log when an exception occurs on a production version which has been obfuscated with ProGuard.
The New Relic system, with the help of this file, will upload the ProGuard mapping.txt file to the New Relic servers and associate it with your app according to the specified token. With this, New Relic can un-obfuscate stack traces and display a descriptive stack trace with actual class & method names, rather a, b, c, etc.
As a workaround, I was told that I can forego this file all together, if I upload the mapping file manually.
The mapping file can be found at:
build/outputs/proguard/release/mapping.txt
In order to manually upload the file, perform the following via command line:
curl -v -F proguard=#"<path_to_mapping.txt>" -H "X-APP-LICENSE-KEY:<APPLICATION_TOKEN>" https://mobile-symbol-upload.newrelic.com/symbol
This must be done for each variant which is being obfuscated with ProGuard (classically, release builds).
Source
Hope this helps someone else.
I solved creating some Gradle tasks. Please, take a look at https://discuss.newrelic.com/t/how-to-set-up-newrelic-properties-file-for-project-with-multiple-build-variants/46176/5
I following a code that worked pretty well for me.
Add the New Relic application token on a string resource file. i.e.: api.xml.
Create a new Gradle file. i.e: newrelic-util.gradle.
Add the following content on the newly created Gradle file:
apply plugin: 'com.android.application'
android.applicationVariants.all { variant ->
//<editor-fold desc="Setup New Relic property file">
def variantName = variant.name.capitalize()
def newRelicTasksGroup = "newrelic"
def projectDirPath = project.getProjectDir().absolutePath
def newRelicPropertyFileName = "newrelic.properties"
def newRelicPropertyFilePath = "${projectDirPath}/${newRelicPropertyFileName}"
// Cleanup task for New Relic property file creation process.
def deleteNewRelicPropertyFile = "deleteNewRelicPropertyFile"
def taskDeleteNewRelicPropertyFile = project.tasks.findByName(deleteNewRelicPropertyFile)
if (!taskDeleteNewRelicPropertyFile) {
taskDeleteNewRelicPropertyFile = tasks.create(name: deleteNewRelicPropertyFile) {
group = newRelicTasksGroup
description = "Delete the newrelic.properties file on project dir."
doLast {
new File("${newRelicPropertyFilePath}").with {
if (exists()) {
logger.lifecycle("Deleting file ${absolutePath}.")
delete()
} else {
logger.lifecycle("Nothing to do. File ${absolutePath} not found.")
}
}
}
}
}
/*
* Fix for warning message reported by task "newRelicMapUploadVariantName"
* Message:
* [newrelic] newrelic.properties was not found! Mapping file for variant [variantName] not uploaded.
* New Relic discussion:
* https://discuss.newrelic.com/t/how-to-set-up-newrelic-properties-file-for-project-with-multiple-build-variants/46176
*/
def requiredTaskName = "assemble${variantName}"
def taskAssembleByVariant = project.tasks.findByName(requiredTaskName)
def createNewRelicPropertyFileVariantName = "createNewRelicPropertyFile${variantName}"
// 0. Searching task candidate to be dependent.
if (taskAssembleByVariant) {
logger.debug("Candidate task to be dependent found: ${taskAssembleByVariant.name}")
// 1. Task creation
def taskCreateNewRelicPropertyFile = tasks.create(name: createNewRelicPropertyFileVariantName) {
group = newRelicTasksGroup
description = "Generate the newrelic.properties file on project dir.\nA key/value propety " +
"will be written in file to be consumed by newRelicMapUploadVariantName task."
logger.debug("Creating task: ${name}")
doLast {
def newRelicPropertyKey = "com.newrelic.application_token"
def newRelicStringResourceKey = "new_relic_key"
def targetResourceFileName = "api.xml"
def variantXmlResourceFilePath = "${projectDirPath}/src/${variant.name}/res/values/${targetResourceFileName}"
def mainXmlResourceFilePath = "${projectDirPath}/src/main/res/values/${targetResourceFileName}"
def xmlResourceFilesPaths = [variantXmlResourceFilePath, mainXmlResourceFilePath]
xmlResourceFilesPaths.any { xmlResourceFilePath ->
// 1.1. Searching xml resource file.
def xmlResourceFile = new File(xmlResourceFilePath)
if (xmlResourceFile.exists()) {
logger.lifecycle("Reading property from xml resource file: ${xmlResourceFilePath}.")
// 1.2. Searching for string name new_relic_key api.xml resource file.
def nodeResources = new XmlParser().parse(xmlResourceFile)
def nodeString = nodeResources.find {
Node nodeString -> nodeString.'#name'.toString() == newRelicStringResourceKey
}
// 1.3. Checking if string name new_relic_key was found.
if (nodeString != null) {
def newRelicApplicationToken = "${nodeString.value()[0]}"
logger.lifecycle("name:${nodeString.'#name'.toString()};" +
"value:${newRelicApplicationToken}")
// 1.4 Checking the content of newRelicApplicationToken
if (newRelicApplicationToken == 'null' || newRelicApplicationToken.allWhitespace) {
logger.warn("Invalid value for key ${newRelicStringResourceKey}. " +
"Please, consider configuring a value for key ${newRelicStringResourceKey}" +
" on ${xmlResourceFile.name} resource file for buildVariant: ${variantName}. " +
"The ${newRelicPropertyFileName} will be not created.")
return true // break the loop
}
// 1.5. File creation.
File fileProperties = new File(newRelicPropertyFilePath)
fileProperties.createNewFile()
logger.lifecycle("File ${fileProperties.absolutePath} created.")
// 1.6. Writing content on properties file.
def fileComments = "File generated dynamically by gradle task ${createNewRelicPropertyFileVariantName}.\n" +
"Don't change it manually.\n" +
"Don't track it on VCS."
new Properties().with {
load(fileProperties.newDataInputStream())
setProperty(newRelicPropertyKey, newRelicApplicationToken.toString())
store(fileProperties.newWriter(), fileComments)
}
logger.lifecycle("Properties saved on file ${fileProperties.absolutePath}.")
return true // break the loop
} else {
logger.warn("The key ${newRelicStringResourceKey} was not found on ${xmlResourceFile.absolutePath}.\n" +
"Please, consider configuring a key/value on ${xmlResourceFile.name} resource file for buildVariant: ${variantName}.")
return // continue to next xmlResourceFilePath
}
} else {
logger.error("Resource file not found: ${xmlResourceFile.absolutePath}")
return // continue next xmlResourceFilePath
}
}
}
}
// 2. Task dependency setup
// To check the task dependencies, use:
// logger.lifecycle("Task ${name} now depends on tasks:")
// dependsOn.forEach { dep -> logger.lifecycle("\tTask: ${dep}") }
tasks['clean'].dependsOn taskDeleteNewRelicPropertyFile
taskCreateNewRelicPropertyFile.dependsOn taskDeleteNewRelicPropertyFile
taskAssembleByVariant.dependsOn taskCreateNewRelicPropertyFile
} else {
logger.error("Required task ${requiredTaskName} was not found. " +
"The task ${createNewRelicPropertyFileVariantName} will be not created.")
}
//</editor-fold>
}
On app/build.gradle file, apply the Gradle file.
apply from: './newrelic-util.gradle'
That’s it. I created a file named newrelic-util.gradle on project app dir. If you execute the task assembleAnyVariantName, the task createNewRelicPropertyFileAnyVarianteName will be performed first. Tip: don’t track the generated file newrelic.properties file. Ignore it on your VCS.
Additionally, the task deleteNewRelicPropertyFile will be performed right before the tasks ‘clean’ and ‘createNewRelicPropertyFileAnyVarianteName’ in order to avoid a file with a wrong New Relic application token.

Categories

Resources