we are developing android app and there are two different environment that we need to point separately. lets say it staging and production but due to compliance requirement we need to provide one apk file and there should be a way to change environment
when its required so that they can make sure that same file go production that they tested on staging.
We are keeping url details in string.xml and normally we give two apks pointing to two diferent environment.
so we are just wandering is there any tool or any other method that we can change string.xml values inside apk. So that they can use that tool to
change url when they required.
Use this kind of way to separate the urls,
buildTypes {
debug {
buildConfigField "Boolean", "IS_DEBUG", 'true'
buildConfigField "String", "URL", 'https://appsgit.com/debugurl'
}
release {
buildConfigField "Boolean", "IS_DEBUG", 'false'
buildConfigField "String", "URL", 'https://appsgit.com/releaseurl'
}
}
Please check this blog post for more info..
If this solution doesn't work. You can try Gradle BuildFlavor.
Add ProductFlavor like below (Free & Paid).
productFlavors {
free {
applicationId "com.appsgit.freeapp"
buildConfigField 'boolean', 'IS_PAID', 'false'
buildConfigField 'boolean', 'URL', 'http://freeversion.com'
applicationVariants.all { variant ->
variant.outputs.each { output ->
output.outputFile = new File(output.outputFile.parent, output.outputFile.name.replace("app-release.apk", "app-free-" + defaultConfig.versionName + ".apk"))
}
}
}
paid {
applicationId "com.appsgit.paidapp"
buildConfigField 'boolean', 'IS_PAID', 'true'
buildConfigField 'boolean', 'URL', 'https://paidversion.com'
versionCode 1
versionName "1.0"
applicationVariants.all { variant ->
variant.outputs.each { output ->
output.outputFile = new File(output.outputFile.parent, output.outputFile.name.replace("app-release.apk", "app-paid-" + defaultConfig.versionName + ".apk"))
}
}
}
}
And create code base like here,
Don't forget Main is shared to all the flavors.
Since strings.xml in your apk will be changed so it will be two different apk for staging and production (different sha256 sum).
If it is not a problem then you can use "flavor" for two different environments.
https://developer.android.com/studio/build/build-variants.html#flavor-dimensions
If it is necessary to have only one apk for staging and production then you can add some kind of killswitch. For example it may be a file with special name. If the file is exist on device then apk use staging path in other way apk use production way.
Hope it will be helpful for you.
You should let the user choose the environment at runtime.
For that, you should read the environment option from the shared preferences.
The easiest way would be to create a Preferences Activity which would have a checkbox to select whether use production or staging environment. Then, map the logic to use the appropriate base URL for when the checkbox is checked or not.
Related
I'm trying to get test coverage report of our Android application module and executing the testVariantBuildTypeUnitTest task.
Although all tests are passed, TEST-classNameTest.xml file contains error message below. This error is only given for methods contain calls Java from Kotlin. Is there any solution for this issue ?
**Caused by: java.lang.IllegalStateException: Cannot process instrumented class... Please supply original non-instrumented classes.
at org.jacoco.agent.rt.internal_f3994fa.core.internal.instr.InstrSupport.assertNotInstrumented(InstrSupport.java:238)
at org.jacoco.agent.rt.internal_f3994fa.core.internal.instr.ClassInstrumenter.visitField(ClassInstrumenter.java:56)
at org.jacoco.agent.rt.internal_f3994fa.asm.ClassVisitor.visitField(ClassVisitor.java:339)
at org.jacoco.agent.rt.internal_f3994fa.asm.ClassReader.readField(ClassReader.java:1111)
at org.jacoco.agent.rt.internal_f3994fa.asm.ClassReader.accept(ClassReader.java:713)
at org.jacoco.agent.rt.internal_f3994fa.asm.ClassReader.accept(ClassReader.java:401)
at org.jacoco.agent.rt.internal_f3994fa.core.instr.Instrumenter.instrument(Instrumenter.java:90)
at org.jacoco.agent.rt.internal_f3994fa.core.instr.Instrumenter.instrument(Instrumenter.java:108)
We have multimodule android project, because of this we're using this custom Jacoco task to get coverage report:
project.afterEvaluate {
(android.hasProperty('applicationVariants')
? android.'applicationVariants'
: android.'libraryVariants')
.all { variant ->
def variantName = variant.name
def unitTestTask = "test${variantName.capitalize()}UnitTest"
def jacocoReportName = unitTestTask + project.name
tasks.create(name: jacocoReportName, type: JacocoReport, dependsOn: [
"$unitTestTask"
]) {
group = "Reporting"
description = "Generate Jacoco coverage reports for the ${variantName.capitalize()} build"
reports {
html.enabled = true
xml.enabled = true
}
def fileFilter = [
// data binding
'android/databinding/**/*.class',
'**/android/databinding/*Binding.class',
'**/android/databinding/*',
'**/androidx/databinding/*',
'**/databinding',
'**/BR.*',
// android
'**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*'
]
def javaClasses = fileTree(dir: variant.javaCompileProvider.get().destinationDir,
excludes: fileFilter)
def kotlinClasses = fileTree(dir: "${buildDir}/tmp/kotlin-classes/${variantName}",
excludes: fileFilter)
classDirectories.setFrom(files([
javaClasses,
kotlinClasses
]))
def variantSourceSets = variant.sourceSets.java.srcDirs.collect { it.path }.flatten()
sourceDirectories.setFrom(project.files(variantSourceSets))
if (isAndroidLibrary(project)) {
executionData(files([
"${projectDir}/jacoco.exec"
]))
}else{
executionData(files([
"$project.buildDir/jacoco/${project.name}.exec"
]))
}
}
}
}
Regards,
Solution
The problem most likely lies in your module level build.gradle file. You need to remove testCoverageEnabled or set it to false.
android {
...
buildTypes {
release {
minifyEnabled true
}
debug {
// testCoverageEnabled true
minifyEnabled false
}
}
Explanation and References
Firstly I've been struggling with unit test coverage on android with JaCoCo for years and repeatedly encountered the issues multiple times as the AGP (Android Gradle Plugin) team enhanced the AGP (e.g. Similar issue) therefore this solution is subject to change in future releases of the AGP.
Come the release of AGP 4.1 the AGP team changed the way the plugin deals with unit tests. I raised an enter link description here issue with the AGP team and they explained that the plugin (AGP 4.1+) does it's own form of instrumentation to get the coverage which is undoubtedly incompatible with JaCoCo. It was in this issue where the solution was highlighted to me.
Note 1: This issue is not documented in the DSL documentation!
Note 2: It should also be noted that as of when I wrote this AGP 7 is in development where the same issue occurs.
Considerations
This solution will cause a problem generating coverage for instrumented tests as the AGP generates coverage for instrumented tests (in the form of a .ec file) meaning to get instrumented test coverage you must have testCoverageEnabled true.
As a workaround you could:
Put your unit tests in the release build variant
Put your instrumented tests in the debug build variant.
It's still possible to combine the coverage into one report: for example:
task jacocoCombinedUnitTestAndroidTestReport(type: JacocoReport) {
group = "Reporting"
reports {
xml.enabled = true
html.enabled = true
}
def mainSrc = "$project.projectDir/src/main/java"
def releaseKotlinClasses = fileTree(dir: "${buildDir}/tmp/kotlin-classes/release", excludes:["com/example/ui**"])
def debugKotlinClasses = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debug/com/example/ui/")
def releaseJavaClasses = fileTree(dir: "${buildDir}/intermediates/javac/release", excludes: ["com/example/ui**"])
def debugJavaClasses = fileTree(dir: "${buildDir}/intermediates/javac/debug/com/example/ui/" )
def mainClasses = releaseKotlinClasses + debugKotlinClasses
def javaClasses = releaseJavaClasses + debugJavaClasses
sourceDirectories.from = files([mainSrc])
classDirectories.from = files([mainClasses, javaClasses])
executionData.from = fileTree(dir: buildDir, include: ["jacoco/testReleaseUnitTest.exec",
"outputs/code_coverage/debugAndroidTest/connected/**.ec"])
}
This assumes you have any UI code in a ui directory.
You include all release classes excluding the ui directory
You only include your ui debug classes.
Please note this is only valid as of when this answer was written and hopefully Google will fix the issue in the future.
I want to code my own minecraft mod and i am following a youtube tutorial.
i did everything right, but in IntelliJ IDEA in the build.gradle file it says "Cannot resolve symbol 'Date'"
here is the full code of the build.gradle file:
buildscript {
repositories {
maven { url = 'https://files.minecraftforge.net/maven' }
jcenter()
mavenCentral()
}
dependencies {
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '4.1.+', changing: true
}
}
apply plugin: 'net.minecraftforge.gradle'
// Only edit below this line, the above code adds and enables the necessary things for Forge to be setup.
apply plugin: 'eclipse'
apply plugin: 'maven-publish'
version = '1.0'
group = 'netjackboi03.forestportal' // http://maven.apache.org/guides/mini/guide-naming-conventions.html
archivesBaseName = 'forestportal'
java.toolchain.languageVersion = JavaLanguageVersion.of(8) // Mojang ships Java 8 to end users, so your mod should target Java 8.
println('Java: ' + System.getProperty('java.version') + ' JVM: ' + System.getProperty('java.vm.version') + '(' + System.getProperty('java.vendor') + ') Arch: ' + System.getProperty('os.arch'))
minecraft {
// The mappings can be changed at any time, and must be in the following format.
// Channel: Version:
// snapshot YYYYMMDD Snapshot are built nightly.
// stable # Stables are built at the discretion of the MCP team.
// official MCVersion Official field/method names from Mojang mapping files
//
// You must be aware of the Mojang license when using the 'official' mappings.
// See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md
//
// Use non-default mappings at your own risk. they may not always work.
// Simply re-run your setup task after changing the mappings to update your workspace.
mappings channel: 'official', version: '1.16.5'
// makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable.
// accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')
// Default run configurations.
// These can be tweaked, removed, or duplicated as needed.
runs {
client {
workingDirectory project.file('run')
// Recommended logging data for a userdev environment
// The markers can be changed as needed.
// "SCAN": For mods scan.
// "REGISTRIES": For firing of registry events.
// "REGISTRYDUMP": For getting the contents of all registries.
property 'forge.logging.markers', 'REGISTRIES'
// Recommended logging level for the console
// You can set various levels here.
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
property 'forge.logging.console.level', 'debug'
mods {
forestportal {
source sourceSets.main
}
}
}
server {
workingDirectory project.file('run')
// Recommended logging data for a userdev environment
// The markers can be changed as needed.
// "SCAN": For mods scan.
// "REGISTRIES": For firing of registry events.
// "REGISTRYDUMP": For getting the contents of all registries.
property 'forge.logging.markers', 'REGISTRIES'
// Recommended logging level for the console
// You can set various levels here.
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
property 'forge.logging.console.level', 'debug'
mods {
forestportal {
source sourceSets.main
}
}
}
data {
workingDirectory project.file('run')
// Recommended logging data for a userdev environment
// The markers can be changed as needed.
// "SCAN": For mods scan.
// "REGISTRIES": For firing of registry events.
// "REGISTRYDUMP": For getting the contents of all registries.
property 'forge.logging.markers', 'REGISTRIES'
// Recommended logging level for the console
// You can set various levels here.
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
property 'forge.logging.console.level', 'debug'
// Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources.
args '--mod', 'forestportal', '--all',
'--existing', file('src/main/resources').toString(),
'--existing', file('src/generated/resources').toString(),
'--output', file('src/generated/resources/')
mods {
forestportal {
source sourceSets.main
}
}
}
}
}
// Include resources generated by data generators.
sourceSets.main.resources { srcDir 'src/generated/resources' }
dependencies {
// Specify the version of Minecraft to use, If this is any group other then 'net.minecraft' it is assumed
// that the dep is a ForgeGradle 'patcher' dependency. And it's patches will be applied.
// The userdev artifact is a special name and will get all sorts of transformations applied to it.
minecraft 'net.minecraftforge:forge:1.16.5-36.0.58'
// You may put jars on which you depend on in ./libs or you may define them like so..
// compile "some.group:artifact:version:classifier"
// compile "some.group:artifact:version"
// Real examples
// compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env
// compile 'com.googlecode.efficient-java-matrix-library:ejml:0.24' // adds ejml to the dev env
// The 'provided' configuration is for optional dependencies that exist at compile-time but might not at runtime.
// provided 'com.mod-buildcraft:buildcraft:6.0.8:dev'
// These dependencies get remapped to your current MCP mappings
// deobf 'com.mod-buildcraft:buildcraft:6.0.8:dev'
// For more info...
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
// http://www.gradle.org/docs/current/userguide/dependency_management.html
}
// Example for how to get properties into the manifest for reading by the runtime..
jar {
manifest {
attributes([
"Specification-Title": "forestportal",
"Specification-Vendor": "examplemodsareus",
"Specification-Version": "1", // We are version 1 of ourselves
"Implementation-Title": project.name,
"Implementation-Version": "${version}",
"Implementation-Vendor" :"examplemodsareus",
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
])
}
}
// Example configuration to allow publishing using the maven-publish task
// This is the preferred method to reobfuscate your jar file
jar.finalizedBy('reobfJar')
// However if you are in a multi-project build, dev time needs unobfed jar files, so you can delay the obfuscation until publishing by doing
//publish.dependsOn('reobfJar')
publishing {
publications {
mavenJava(MavenPublication) {
artifact jar
}
}
repositories {
maven {
url "file:///${project.projectDir}/mcmodsrepo"
}
}
}
The problem lies in this line of code:
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
I don't know if this is significant to run the mod, but i wanted to make sure it isn't.
Thanks for your help :)
I need to say that I don't know. On 1.16.4 it works perfectly, but I have no idea why this don't work on 1.16.5. Maybe try to delete this line and then see if everything works well without this line.
I am working on a app for school in android studio using java
I want to generate multiple apk for school with their school name and logo, (about 50+ schools)
Is there any way to save time generating one by one
in future need to update all
Read official guideline about productFlavors.
Creating product flavors is similar to creating build types: add them
to the productFlavors block in your build configuration and include
the settings you want. The product flavors support the same properties
as defaultConfig—this is because defaultConfig actually belongs to the
ProductFlavor class.
productFlavors {
elvispresley {
applicationId 'your_package_id'
versionCode 1
versionName '1.0'
}
whitneyhouston {
applicationId 'your_package_id_2'
versionCode 2
versionName '2.0.1'
}
projectOne {
applicationIdSuffix ".one"
}
projectTwo {
applicationIdSuffix ".two"
}
}
I am trying to assign value to below meta in manifest.
currently i have assign value from string.xml class.
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value= "#string/MAPS_API_KEY" />
But I want Some thing like this..
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value= com.packagename.MyConstantInterface.MAPS_API_KEY />
I have search a lot, but haven't find a good solution.
NOTE: I am getting all APIs keys from server, storing keys in snappy db and then assigning keys from snappy db to MyConstantInterface.
STEP 1: Create a file named secrets.properties in the main folder (i.e below local.properties, app, build, gradle, README.md,etc.
STEP 2: Paste your API Key in secrets.properties (i.e GOOGLE_API_KEY, FACEBOOK_APP_ID, etc)
STEP 3: Sync the project or Rebuild.
STEP 4: Open build.gradle (app) and create a def function to access the key declared in the secrets.properties.
def getApiKey(){
def Properties props = new Properties()
props.load(new FileInputStream(new File('secrets.properties')))
return props['GOOGLE_MAPS_API_KEY']
}
STEP 5: Create a variable for the function getApiKey() in defaultConfig using manifestPlaceholders to use it in AndroidManifest.xml
defaultConfig {
defaultPublishConfig 'debug'
applicationId "YOUR_APPLICATION_ID"
minSdkVersion 19
targetSdkVersion 27
versionCode 1000
versionName '0.1.0'
manifestPlaceholders = [ GOOGLE_MAPS_API_KEY:getApiKey()]
}
You’re good to go. Now GOOGLE_MAPS_API_KEY variable is public and can be used in AndroidManifest.xml like below
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${GOOGLE_MAPS_API_KEY}" />
manifestPlaceholders — It helps to create a global variable that can be used only in AndroidManifest.xml
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt')
, 'proguard-rules.pro'
}
applicationVariants.all { variant ->
variant.buildConfigField "String", "GOOGLE_MAPS_API_KEY"
, "\""+getApiKey()+"\""
}
}
And, you can use GOOGLE_MAPS_API_KEY in Java or Kotlin classes like
BuildConfig.GOOGLE_MAPS_API_KEY
Finally, don’t forget to add secrets.properties to your .gitignore file.
Solution was gotten from This Medium post by Chandrasekar Kappusamy
You simply can not simply use a java variable.
Instead of it, You need to inject build variables into the manifest
Declaration:
defaultConfig {
...
manifestPlaceholders = [MAPS_API_KEY_FOR_MANIFEST: "your_maps_key_here"] // TO use in manifest file
buildConfigField "String", "MAPS_API_KEY", '"your_maps_key_here"' // TO use in java file
}
Use in AndroidManifest.xml:
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value= "${MAPS_API_KEY_FOR_MANIFEST}" />
Java file Usage:
BuildConfig.MAPS_API_KEY
For more information, You can have a look at this and this.
i got this error from gradle when i try to build the project after i put the api key in gradle file
android {
.....
buildTypes.each {
it.buildConfigField 'String', 'cedf6......80c62', MyOpenWeatherMapApiKey
}
}
this error i got when i build the project :-
Error:(22, 0) Could not find property 'MyOpenWeatherMapApiKey' on com.android.build.gradle.AppExtension_Decorated#48bee3b8.
Open File
i think i have to make a file in some folder , but i don't know in which folder ? ,
and what i ishould to type inside the file after i create it ?
Since you are using a String you have to use this syntax:
buildConfigField "String" , "OPEN_WEATHER_MAP_API_KEY" , "\"XXXXX-XXXXX-XXX\""
The last parameter has to be a String
Otherwise you can use something like this:
resValue "string", "OPEN_WEATHER_MAP_API_KEY", "\"XXXXX-XXXXX-XXX\""
The first case generates a constants iin your BuildConfig file.
The second case generates a string resource value that can be accessed using the #string/OPEN_WEATHER_MAP_API_KEY annotation.
Credits: Could not find property 'xxxx' on com.android.build.gradle.AppExtension_Decorated
Alternatively, you can use a combination of double quotes and single quotes to avoid the escape characters (where xxx is your API):
buildTypes.each {
it.buildConfigField 'String', 'OPEN_WEATHER_MAP_API_KEY', '"xxxxxxxxx"'
}