How to schedule a `lint` error (update: use papercut) - android

I'm looking for a way to temporarily suppress a lint error or to schedule an error after a particular date or condition.
Here's some context:
I have an android app that is translated to multiple languages and I have set it up so that lint fails with an error if there are missing translations. Evidently this is done so that untranslated strings don't escape into the wild.
Whenever there's a new string resource, it takes a while until it gets translated, during which lint fails.
This can be suppressed or the strings can be marked as untranslatable but that beats the purpose of having the check in the first place.
Aside from that context, there are plenty more instances when a particular setting is "temporary". I don't like having to remember to flip back every switch.
The builds are on a CI server, releases are often and this happens in a team where anyone is able to "temporarily" ignore warnings.
Is there an automagical way to make sure things don't get ignored upon release?
Update:
Since asking this question, someone has developed a library that does just what I asked and more: https://github.com/Stuie/papercut

I don't know how you determin when it is time to de/activate lint. But maybe this helps:
You can switch Lint on/off with a boolean in the gradle script:
android {
lintOptions {
if (lintOn){
checkReleaseBuilds true
abortOnError true
} else {
checkReleaseBuilds false
abortOnError false
}
}
}
Add lintOn=false to your gradle.properties, otherwise gradle won't recognize it as variable.
Now you cann add a task and make it run before the build task:
task preBuild << {
// do stuff to determin if lint should run
lintOn = true
}
build.dependsOn preBuild
In this preBuild task you can now implement some code to check if you should run lint or not.
Here are some good examples of what a task can do.

Related

Android - set all lint warnings as errors except for certain ones

I am trying to make my continuous integration fail the build when new lint warnings that aren't in the lint-baseline.xml file are introduced. I want to have all lint warnings treated as errors (so the build is aborted), but I'd like a way to specify certain lint checks to be treated as informational or warning level so that they still appear in the lint results, but don't cause the build to be aborted.
Here is an example of basically what I'd like to do (except this doesn't work, the build fails if any non-ignored warnings exist):
lintOptions {
lintConfig file("lint.xml")
baseline file("lint-baseline.xml")
checkAllWarnings true
warningsAsErrors true
abortOnError true
informational 'MissingTranslation, ...' // don't fail the build for these
}
Is there an easy way to treat all lint checks as errors, excluding certain ones? I thought about manually setting all 200+ lint checks to the error level, but that wouldn't be very future proof, since I'd have to update the list every time new lint checks were added.
You should be able to achieve what you want if you do not use the Gradle lintOptions (checkAllWarnings, warningsAsErrors, etc.) to configure which warnings should be treated as errors. Use lint.xml instead. There you can do the following:
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="MissingTranslation" severity="warning" />
<!-- The following must be at the bottom of your file!
All lint issues (not listed above) will be treated as errors. -->
<issue id="all" severity="error" />
</lint>
In my tests this seemed to work fine and all warnings were treated as errors except for those listed at the top of the lint.xml.
However, I've not tested it in combination with a lint-baseline.xml but I see no reason why it shouldn't work there as well.
For me, this configuration worked:
android {
lintOptions {
warningsAsErrors true
warning 'MissingTranslation', ...
}
}
It seems the options are evaluated in the "correct order" (aka "as I need it"), i.e. first all warnings are elevated to errors, then this settings is overriden again for a single issue id. Using warning instead of disable or ignore ensures the issues are still visible in the report or the IDE.
It doesnt seem informational is a real option from this doc, I suggest:
android {
lintOptions {
checkAllWarnings true
warningsAsErrors true
// use this line to check all rules except those listed
disable 'MissingTranslation', ...
//OR this line to check but not worry about result (i think this is what you want)
ignore 'MissingTranslation', ...
}
}

Mark part of code as "it can not be in release"

Is there any method to mark some place it code, which has to be improved? I seen it before somewhere but can not remember now. It blocks release build, and it is not TODO comment. What is it?
I've found. It is StopShip lint check.
Enable it in build.gradle:
android {
...
lintOptions {
abortOnError true
fatal 'StopShip'
}
}
If you have a //STOPSHIP comment in your code, this will cause an error to be thrown when a release apk is generated.
You can turn on //STOPSHIP highlighting in Android Studio (wasn't enabled by default for me) in Preferences > Editor > Code Style > Inspections. Search for STOPSHIP to find the correct setting.
Source: https://www.reddit.com/r/androiddev/comments/5c8b0a/i_know_android_studio_allows_you_to_make_custom/d9uhdzt/
I don't think there is a native way provided to do so.
You may however do something like this, by control a single boolean:
static boolean isDebug = false;
assert isDebug = true;
if (isDebug)
{
/* Do stuff only for debug builds */
}
We need to use BuildConfig.DEBUG
if (BuildConfig.DEBUG)
{
/* Do stuff only for debug builds */
}
Its the proper way for writing some code only for Debug and no need to use any variables to check for debug as BuildConfig.DEBUG its taken care by Android run system depending on your Build Config.

How to add conditional afterEvaluate on buildType in Gradle

I have this code at the end of my build.gradle file:
project.afterEvaluate {
// If you add/change build types, you have to add to/change
// these task names.
mergeDebugAssets.dependsOn project.tasks.getByName('downloadLicenses')
mergeReleaseAssets.dependsOn project.tasks.getByName('downloadLicenses')
}
See: Copy generated third party licenses to assets for the full code
This used to work fine (regardless of which buildType was set) but if updating my dependencies to the latest version this triggers an exception (when building with buildType='debug'):
Could not get unknown property 'mergeReleaseAssets' for project ':application'
My thought was that maybe split this block in two and put them under the buildTypes configuration. This doesn't work though, as it tries to evaluate the code anyway and crashes.
Any ideas?
Update 1: Root cause?
https://code.google.com/p/android/issues/detail?id=219732
Update 2: A horrible workaround:
try {
mergeDebugAssets.dependsOn project.tasks.getByName('downloadLicenses')
} catch (Exception e) {
// Expected when building variant Release
}
try {
mergeReleaseAssets.dependsOn project.tasks.getByName('downloadLicenses')
} catch (Exception e) {
// Expected when building variant Debug
}
The reason that you get an error referencing a release task in afterEvaluate is probably because Android Studio's Instant Run feature uses a special Android Gradle plugin feature to only build the debug application variant. This means that only the debug tasks are created, hence why the release tasks can't be found when you reference them.
There are several ways to deal with this:
Search for the dependent task by name using a string. If the Android build system changes the task name in the future, your additional build rules won't run, but you might not even notice.
To determine if release tasks exist, check for the existence of a task whose name is unlikely to change in the future. Maybe assembleRelease fits this bill.
To determine if release tasks exist, check if the release application variant is built with something like:
project.afterEvaluate {
if (!android.applicationVariants.matching { it.buildType.name == 'release' }.isEmpty()) {
mergeReleaseAssets.dependsOn project.tasks.getByName('downloadLicenses')
}
}
The last option looks kind of gross, but hopefully it will fail-fast if the Android build system task names change in the future.
The reason you get exception in case of debug buildType is because mergeReleaseAssets task is not created. You can do the following instead:
project.tasks.findByName('mergeReleaseAssets')?.dependsOn project.tasks.getByName('downloadLicenses')
or even omit project:
tasks.findByName('mergeReleaseAssets')?.dependsOn tasks.getByName('downloadLicenses')
This uses safe navigation operator on nullable return type, so it's clearer than try/catch workaround.

Android app build throws error for release, debug doesn't

I'm trying to build an Android app that I've been working on (it's a project I adopted so most of the code isn't mine - which isn't helping :P) and I'm running into an issue.
The app builds just fine in debug mode (building and installing it on a device for testing). But when I try to build a release it fails.
This is the error in the gradle console:
Execution failed for task ':app:lintVitalRelease'.
Lint found fatal errors while assembling a release target.
And this is what it says in Messages Gradle Build:
Error:Error: This fragment class should be public ([com.company.appname].fragments.create_dilemma.CreateDilemmaFragment1_2.UploadDialogFragment) [ValidFragment]
This is the offending line:
private class UploadDialogFragment extends DialogFragment implements View.OnClickListener
So I change private to public and then it complains that it should be a static class. Thing is, it looks like this class isn't supposed to be static because AS isn't happy with pretty much any of the code as soon as I make it static.
So I'm left with a few questions:
How is this only an issue for the release build and not for debug?
Is there a way to ignore this 'error' when building a release?
There must be a reason for this error, right? Why is it ignored for debug and not for release? What are the up/downsides of fixing this? Because the app works just fine as far as I can tell so I don't really see the problem..?
PS: My java skills are so-so. I know my way around the language but I have a lot to learn when it comes to knowing what a static class exactly is and what is allowed and what not, why it is(n't), etc. So plz be gentle, I'm trying to learn this stuff :)
Update: As per request here's the relevant part of my build.gradle:
android {
compileSdkVersion 24
buildToolsVersion '24.0.1'
useLibrary 'org.apache.http.legacy'
defaultConfig {
applicationId "[com.pany.appname]"
minSdkVersion 15
targetSdkVersion 22
multiDexEnabled true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
productFlavors {
}
}
I'm summarizing some of the answers given in this post to collect them in one. Makes it easier to accept it, too :)
-- WHY THE QUICK FIX IS NOT A REAL FIX --
It's POSSIBLE to have this project build a release apk successfully (see below). But it's probably not a good idea to just do that and not think about it.
As pointed out by X3Btel:
Fragments needs to be public because systems recreates them on orientation change. The other warning is because non static inner classes holds reference to their outter class, hence it creates memmory leak. Best course of action would be move the fragment to his own class. Or make it public and ignore the lint warning (this may create memmory leak but keeping it private may crash the app)
In my case I can keep it non-static and circumvent memory leaks by making sure I finish the fragment whenever its parent activity is finished. It's not pretty but it'll fix the memory leak and I don't have to refactor A LOT of code I didn't write. That said: It apparently is bad practice to have an activity or fragment and declare another activity/fragment as an inner class because of the way the Android lifecycle works.
Here's some more reading on the topic which I found useful (and only found AFTER I posted this question):
Should an internal DialogFragment class be static or not?
-- THE QUICK FIXES --
1) Don't check for lint errors during build (as pointed out by Jay Shan)
Add lintOptions -> checkReleaseBuilds option to build.gradle
android {
// ..
lintOptions {
checkReleaseBuilds false
}
}
2) Check for errors but keep building even when they are found
This is probably a little bit safer than not checking for errors at all because at least you'll get a warning somewhere in the log output.
Add lintOptions -> abortOnError option to build.gradle
android {
// ..
lintOptions {
abortOnError false
}
}
3) Supress the error where it happens
I find this to be the preferred method because you can still use lint for finding other problems AND have it abort when that happens BUT at the same time you can ignore things you've checked.
In my case I had to add #SuppressLint("ValidFragment") before the offending line:
#SuppressLint("ValidFragment")
private class UploadDialogFragment extends DialogFragment implements View.OnClickListener
{
// ..
}
UPDATE 2018/01/04
If you use a recent version of Android Support Library (and its Fragment implementation instead of the OS's) your app will crash (IllegalStateException) if you try to initialize that Fragment. Suppressing the warning will not help you. You'll just have to fix the underlying problem, make the inner class public and static, or move the class to a separate file.
You can put this option in android block of build.gradle section
lintOptions {
checkReleaseBuilds false
}
Jay Shan`s answer should work. But better to understand what is the problem. Fragments needs to be public because systems recreates them on orientation change. The other warning is because non static inner classes holds reference to their outter class, hence it creates memmory leak.
Best course of action would be move the fragment to his own class. Or make it public and ignore the lint warning (this may create memmory leak but keeping it private may crash the app)

How to sign APK on Android Studio even with non-translated strings?

Background
I've recently migrated my app to Android-Studio. I had some issues doing so, but I got over them eventually.
The problem
For some reason, on Android Studio, when I try to sign an APK, I get a lot of errors that look like this:
Error:(16) Error: "..." is not translated in "de" (German), "el" (Greek), "iw" (Hebrew) [MissingTranslation]
(where "..." is a string)
At the bottom, after a lot of errors of this kind, I see this:
Error:Execution failed for task ':app:lintVitalRelease'.
> Lint found fatal errors while assembling a release target.
To proceed, either fix the issues identified by lint, or modify your build script as follows:
...
android {
lintOptions {
checkReleaseBuilds false
// Or, if you prefer, you can continue to check for errors in release builds,
// but continue the build even when errors are found:
abortOnError false
}
}
...
The question
I'm not sure what's wrong and how I can fix it. On Eclipse I did it very easily. Missing translations shouldn't stop me from signing an APK...
To me it seems as if Lint is preventing the exporting of the APK, and that the reason is that I didn't translate all of the strings. Is that true?
Can anyone please help me? How can I fix this, so that Lint will show me just warnings instead? or a confirmation dialog if I'm sure I want to do it?
The cleanest way to solve the problem is to disable Lint checks of missing translations for release builds only.
To do so add "disable 'MissingTranslation'" to your build.gradle file as shown below:
android {
buildTypes {
release {
lintOptions {
disable 'MissingTranslation'
}
}
}
}
To me it seems as if Lint is preventing the exporting of the APK, and
that the reason is that I didn't translate all of the strings. Is that
true?
Yes. Default option is lintOptions.abortOnError = true
Can anyone please help me?
You should open the build.gradle file located at the main project module, or the generic folder if you do not have a module. Then add the suggested lines:
android {
lintOptions {
checkReleaseBuilds false
// Or, if you prefer, you can continue to check for errors in release builds,
// but continue the build even when errors are found:
abortOnError false
}
}
Some Lint warnings are by default turned to studio as errors, I don't actually know why, but in terms of translations I guess that is a way to "stop" you publishing an app that the translation is incomplete due to a last minute additions of some texts.
With the lintOptions checkReleaseBuilds abortOnError you set the checking of Lint not to run for release versions and also not stopping if an "error" is found. Below I explain where the Lint errors settings can be found, so if you want to go further you can go one step forward and read them one by one. Some of them provide helpful instructions for code optimizations.
How can I fix this, so that Lint will show me just warnings instead?
or a confirmation dialog if I'm sure I want to do it?
There is also an option at the Android Studio settings to change any Lint error to Lint warning, but I never test that. I usually turn to the gradle solution.
The option is located at Settings > Inspections > Android Lint. For easy find open Settings and at the search (located at the top) type Lint translation there you can change the translation options appear at the left from errors to warnings.
An other option if your error strings never going to be translated is to add at your XML string files tools:ignore="MissingTranslation" either at the root item or at each non-translatable string.
Simple way to solve this Error
Just add following Code To do add "disable 'MissingTranslation'" to your build.gradle file as shown below:
...
android {
lintOptions {
checkReleaseBuilds false
// Or, if you prefer, you can continue to check for errors in release builds,
// but continue the build even when errors are found:
abortOnError false
}
}
...
OR You can also Add this:
android {
buildTypes {
release {
lintOptions {
disable 'MissingTranslation'
}
}
}
}
You could try to open "Translations Editor" and set the string "..." as "Unstranlatable".
You also must remove all translations of this string.
FWIW: If you don't plan on supporting other languages, then you don't need to disable the lint checks at all. Sometimes your project setup (or a library you're importing) may have accidentally - or intentionally - included a config to support additional languages by declaring a values- folder for that language like this for instance:
<your project source folder>/main/res/values-ar
This was the case for me so I simply removed the folder. But if you have no control over the offending library then one choice is to disable lint abortOnError as indicated in the accepted answer, or find a way to exclude 'library-imported' folders somehow. For the latter option you can start here
there is many solution but i tried
<string name="hello" translatable="false">hello</string>
It's the ignore attribute of the tools namespace in your strings file, as follows:
<?xml version="1.0" encoding="utf-8"?>
<resources
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="MissingTranslation" >
<!-- your strings here; no need now for the translatable attribute -->
</resources>
and from the Gradle
release {
lintOptions {
disable 'MissingTranslation'
}
}
and
android {
lintOptions {
disable 'MissingTranslation'
}
}
Working
buildTypes {
release {
lintOptions {
checkReleaseBuilds false
abortOnError false
}
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

Categories

Resources