Java compiler does not preserve parameter names for any interface, unless newer compiler option -parameter is used (I am not sure how to use it with android studio) - refer example below.
Since java compiler does not preserve parameters names, Android Studio "code -> implement methods" is not able to generate code with original parameter names.
The question is, how to implement a library module so that Android Studio Menu, Code->Implement Methods correctly generates code with all the original parameter names.
For example, following is a simple class and an interface. This class is in a separate aar module. When application uses this AAR, implements TablaListener and asks AndroidStudio to generate interface methods stubs, the parameter names are not preserved.
Please note that proguard is NOT used.
Any ideas?
public class TablaCore {
public interface TablaListener {
/**
* #param params
* #param data
* #return
*/
boolean TablaCore_onAction(String params, byte[] data);
}
private static TablaListener mListener = null;
public static void setListener(TablaListener myListener) {
mListener = myListener;
}
public TablaListener getListener() {
return mListener;
}
}
It is easy to demonstrate by compiling and decompiling above class. This is decompiled version
public class TablaCore
{
private static TablaListener mListener = null;
public static void setListener(TablaListener myListener)
{
mListener = myListener;
}
public TablaListener getListener()
{
return mListener;
}
public static abstract interface TablaListener
{
public abstract boolean TablaCore_onAction(String paramMessageParams, byte[] paramArrayOfByte);
}
}
You have to include Android SDK source code.
Go to: File > Settings... > Apperance & Behavior > System Settings > Android SDK
On the tab SDK Platforms select Show Package Details and find and select appropriate Sources for Android XX. Unfortunately, currently for Android API 27, there is no sources, but there is for Sources for Android 26. Apply changes - the download window should appear automatically.
After downloading and restarting implementing methods should use proper names for method parameters.
I'm following this tutorial and this Custom Detector Example in order to implement Custom Lint Rules. Basically what I've done is:
Create a new Android Project in Android Studio;
Create a java module for project created in step 1;
On module's build.gradle, import Lint API dependencies;
Create an Issue & IssueRegistry & CustomDetector;
Reference the IssueRegistry on module's build.gradle;
Create Unit tests;
My problem is, during the execution of my JUnits, I always receive "No Warning". When I debug the test, I can see that my Custom Detector isn't called, what am I doing wrong?
Strings.java
public class Strings {
public static final String STR_ISSUE_001_ID = "VarsMustHaveMoreThanOneCharacter";
public static final String STR_ISSUE_001_DESCRIPTION = "Avoid naming variables with only one character";
public static final String STR_ISSUE_001_EXPLANATION = "Variables named with only one character do not pass any meaning to the reader. " +
"Variables name should clear indicate the meaning of the value it is holding";
}
Issues.java
public class Issues {
public static final
Issue ISSUE_001 = Issue.create(
STR_ISSUE_001_ID,
STR_ISSUE_001_DESCRIPTION,
STR_ISSUE_001_EXPLANATION,
SECURITY,
// Priority ranging from 0 to 10 in severeness
6,
WARNING,
new Implementation(VariableNameDetector.class, ALL_RESOURCES_SCOPE)
);
}
IssuesRegistry.java
public class IssueRegistry extends com.android.tools.lint.client.api.IssueRegistry {
#Override
public List<Issue> getIssues() {
List<Issue> issues = new ArrayList<>();
issues.add(ISSUE_001);
return issues;
}
}
VariableNameDetector.java
public class VariableNameDetector extends Detector implements Detector.JavaScanner {
public VariableNameDetector() {
}
#Override
public boolean appliesToResourceRefs() {
return false;
}
#Override
public boolean appliesTo(Context context, File file) {
return true;
}
#Override
#Nullable
public AstVisitor createJavaVisitor(JavaContext context) {
return new NamingConventionVisitor(context);
}
#Override
public List<String> getApplicableMethodNames() {
return null;
}
#Override
public List<Class<? extends Node>> getApplicableNodeTypes() {
List<Class<? extends Node>> types = new ArrayList<>(1);
types.add(lombok.ast.VariableDeclaration.class);
return types;
}
#Override
public void visitMethod(
JavaContext context,
AstVisitor visitor,
MethodInvocation methodInvocation
) {
}
#Override
public void visitResourceReference(
JavaContext context,
AstVisitor visitor,
Node node,
String type,
String name,
boolean isFramework
) {
}
private class NamingConventionVisitor extends ForwardingAstVisitor {
private final JavaContext context;
NamingConventionVisitor(JavaContext context) {
this.context = context;
}
#Override
public boolean visitVariableDeclaration(VariableDeclaration node) {
StrictListAccessor<VariableDefinitionEntry, VariableDeclaration> varDefinitions =
node.getVariableDefinitionEntries();
for (VariableDefinitionEntry varDefinition : varDefinitions) {
String name = varDefinition.astName().astValue();
if (name.length() == 1) {
context.report(
ISSUE_001,
context.getLocation(node),
STR_ISSUE_001_DESCRIPTION
);
return true;
}
}
return false;
}
}
}
build.gradle
apply plugin: 'java'
configurations {
lintChecks
}
ext {
VERSION_LINT_API = '24.3.1'
VERSION_LINT_API_TESTS = '24.3.1'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "com.android.tools.lint:lint-api:$VERSION_LINT_API"
implementation "com.android.tools.lint:lint-checks:$VERSION_LINT_API"
testImplementation "com.android.tools.lint:lint-tests:$VERSION_LINT_API_TESTS"
}
jar {
manifest {
attributes('Lint-Registry': 'br.com.edsilfer.lint_rules.resources.IssueRegistry')
}
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
TestVariableNameDetector.java
private static final String ARG_DEFAULT_LINT_SUCCESS_LOG = "No warnings.";
#Override
protected Detector getDetector() {
return new VariableNameDetector();
}
#Override
protected List<Issue> getIssues() {
return Collections.singletonList(Issues.ISSUE_001);
}
public void test_file_with_no_variables_with_length_equals_01() throws Exception {
assertEquals(
ARG_DEFAULT_LINT_SUCCESS_LOG,
lintProject(java("assets/Test.java", "public class Test {public String sampleVariable;}"))
);
}
public void test_file_with_variables_with_length_equals_01() throws Exception {
assertEquals(
ARG_DEFAULT_LINT_SUCCESS_LOG,
lintProject(java("assets/Test3.java", "public class Test {public String a;bnvhgvhj}"))
);
}
}
P.S.: on Java's module I do not have access to assetsor res folder, that is the reason why I've created a String.java and I'm using java(to, source) in my Unit test - I assumed that this java method does the same as the xml from the tutorial link I referenced at the top of this question.
It turned out that in my case the problem was with the JUnit itself. I think that the way I was attempting to simulate the file was wrong. The text below is part of the README.md of a sample project that I've created in order to document what I've learned from this API and answers the question in the title:
Create
Create a new Android Project;
Create a new Java Library Module - Custom Lint Rules are packaged into .jar libraries once they are ready, therefore the easiest way to implement them using them is inside a Java Module Library;
On module's build.gradle:
add target and source compatibility to Java 1.7;
add dependencies for lint-api, lint-checks and test dependencies;
add jar packing task containing two attributes: Manifest-Version and Lint-Registry, set the first to 1.0 and the second as the full path to a class that will later on contain the issue's catalog;
add a default tasl assemble;
[OPTIONAL]: add a task that will copy the generated .jar into ~/.android/lint;
Check REF001 and choose a Detector that best suits your needs, based on it create and implement a class to fulfill the Detector's role;
Still based on REF0001 chosen file, create and implement a Checker class, later referring to it inside Detector's createJavaVisitor() method;
for the sake of SRP, do not place Checker in the same file of Detector's class;
Copy the generated .jar file from build/lib to ~/.android/lint - if you added a task on build.gradle that does this you can skip this step;
Restart the computer - once created and moved into ~/.android/lint, the Custom Rules should be read by Lint next time the program starts. In order to have the alert boxes inside Android Studio, it is enough to invalidate caches and restart the IDE, however, to have your custom rules caught on Lint Report when ./gradlew check, it might be necessary to restart your computer;
Testing Detectors and Checkers
Testing Custom Rules is not an easy task to do - mainly due the lack of documentation for official APIs. This section will present two approaches for dealing with this. The main goal of this project is to create custom rules that will be run against real files, therefore, test files will be necessary for testing them. They can be places in src/test/resources folder from your Lint Java Library Module;
Approach 01: LintDetectorTest
Make sure you've added all test dependencies - checkout sample project's build.gradle;
Copy EnhancedLintDetectorTest.java and FileUtils.java into your project's test directory;
There is a known bug with Android Studio that prevents it from seeing files from src/test/resources folder, these files are a workaround for that;
EnhancedLintDetectorTest.java should return all issues that will be subject of tests. A nice way to do so is getting them from Issue Registry;
Create a test class that extends from EnhancedLintDetectorTest.java;
Implement getDetector() method returning an instance of the Detector to be tested;
Use lintFiles("test file path taking resources dir as root") to perform the check of the Custom Rules and use its result object to assert the tests;
Note that LintDetectorTest.java derives from TestCase.java, therefore, you're limited to JUnit 3.
Approach 02: Lint JUnit Rule
You might have noticed that Approach 01 might be a little overcomplicated, despite the fact that you're limited to JUnit 3 features. Because of that GitHub user a11n created a Lint JUnit Rule that allows the test of Custom Lint Rules in a easier way that counts with JUnit 4 and up features. Please, refer to his project README.md for details about how to create tests using this apprach.
Currently, Lint JUnit Rule do not correct the root dir for test files and you might no be able to see the tests passing from the IDE - however it works when test are run from command line. An issue and PR were created in order to fix this bug.
I'm not sure how to use the AST Api, however I'm personally using Psi and this is one of my lint checks that are working for me.
public final class RxJava2MethodCheckReturnValueDetector extends Detector implements Detector.JavaPsiScanner {
static final Issue ISSUE_METHOD_MISSING_CHECK_RETURN_VALUE =
Issue.create("MethodMissingCheckReturnValue", "Method is missing the #CheckReturnValue annotation",
"Methods returning RxJava Reactive Types should be annotated with the #CheckReturnValue annotation.",
MESSAGES, 8, WARNING,
new Implementation(RxJava2MethodCheckReturnValueDetector.class, EnumSet.of(JAVA_FILE, TEST_SOURCES)));
#Override public List<Class<? extends PsiElement>> getApplicablePsiTypes() {
return Collections.<Class<? extends PsiElement>>singletonList(PsiMethod.class);
}
#Override public JavaElementVisitor createPsiVisitor(#NonNull final JavaContext context) {
return new CheckReturnValueVisitor(context);
}
static class CheckReturnValueVisitor extends JavaElementVisitor {
private final JavaContext context;
CheckReturnValueVisitor(final JavaContext context) {
this.context = context;
}
#Override public void visitMethod(final PsiMethod method) {
final PsiType returnType = method.getReturnType();
if (returnType != null && Utils.isRxJava2TypeThatRequiresCheckReturnValueAnnotation(returnType)) {
final PsiAnnotation[] annotations = method.getModifierList().getAnnotations();
for (final PsiAnnotation annotation : annotations) {
if ("io.reactivex.annotations.CheckReturnValue".equals(annotation.getQualifiedName())) {
return;
}
}
final boolean isMethodMissingCheckReturnValueSuppressed = context.getDriver().isSuppressed(context, ISSUE_METHOD_MISSING_CHECK_RETURN_VALUE, method);
if (!isMethodMissingCheckReturnValueSuppressed) {
context.report(ISSUE_METHOD_MISSING_CHECK_RETURN_VALUE, context.getLocation(method.getNameIdentifier()), "Method should have #CheckReturnValue annotation");
}
}
}
}
}
Checkout the many more I wrote here.
I need to get dimensions defined in XML files under Android folder from classes in Core directory of libgdx.
I know I can use assets folder for accessing drawables. But how about dimensions or strings?
You can't access this kind of platform specific information directly in the core project. But this can be achieved by interfacing these functions with a Facade, providing a platform-specific implementation for each target.
In libgdx-wiki there is an example for platform specific interfaces.
public interface PlatformSpecificStuff {
public String getString();
public int getDimensions();
}
// In -Android project
public class AndroidSpecificStuff implements PlatformSpecificStuff {
public String getString(){
// do whaterver you want with the resources.
return "string resource";
}
public int getDimensions(){
// do whaterver you want with the resources.
return 42;
}
}
// In -Desktop project
public class DesktopSpecificStuff implements PlatformSpecificStuff {
public String getString(){
// there is no xml or any andorid specific resource
return null;
}
public int getDimensions(){
// there is no xml or any andorid specific resource
return 0;
}
}
public class MyGame implements ApplicationListener {
private final PlatformSpecificStuff pss;
public MyGame(PlatformSpecificStuff pss) {
this.pss= pss;
}
}
Update: I don't know what are trying to accomplish by getting the dimensions as android resources. But you are probably in wrong track. If you want to support multiple screen sizes, have a look at viewports
I'm trying to write a Unity plugin that will work both on iOS and Android.
I have two point of entry to my plugin, one class for iOS and another for Android.
The problem is when I use only the iOS class and try to build the XCode project from Unity I get this error:
The class UnityEngine.AndroidJavaObject could not be loaded, used in UnityEngine
AndroidJavaObject class is used only in the Android class which is never referenced by the unity scripts.
The problem is I want to provide one plugin that will "just work" for both iOS and Android. The plugin is compiled so I can't use any of the unity pre-processor flags.
How can I solve this issue?
I see no alternative to conditional compilation if you need access to the native API. I would suggest to use encapsulate plugin access by using an interface, for example:
public interface IMyPlugin
{
void Do ();
}
public class MyIOSConnector : IMyPlugin
{
public void Do () {
#if UNITY_IOS
// iOS implementation
#endif
}
}
public class MyAndroidConnector : IMyPlugin
{
public void Do () {
#if UNITY_ANDROID
// Android implementation
#endif
}
}
public static class MyPlugin
{
static IMyPlugin _myPlugin = null;
public static IMyPlugin Access {
get {
if (_myPlugin == null) {
#if UNITY_EDITOR
// maybe this needs special handling e.g. a dummy implementation
#endif
switch (Application.platform) {
case RuntimePlatform.IPhonePlayer:
_myPlugin = new MyIOSConnector ();
break;
case RuntimePlatform.Android:
_myPlugin = new MyAndroidConnector ();
break;
default:
break;
}
}
return _myPlugin;
}
}
}
From other classes you call then:
MyPlugin.Access.Do ();
I am working on a project hosted on AppEngine, and for the browser client I am using the GWTP platform which implies using GIN (on the client) and GUICE on the server. Also, it uses Models, presenters, actions and events.
I am thinking of also writing an android client for the service but I don't know how to start because I don't know how to connect and exchange data with the webservice. I would have to use Actions and Action Handlers ( http://code.google.com/p/gwt-platform/wiki/GettingStartedDispatch ) which I use for the browser client. From Android I only know how to do it with RPC, and I can't make the connection, I don't know how to map classes from the device to the server.
For example, by using GWTP, if on the browser client I want to do something on the server, I implement an Action class, an ActionResult class ( both on the client ) and an ActionHandler class (on the server). To dispatch an action, I use the DispatchAsync interface and to get the result I use AsyncCallback.
Action (on the client ) - SendRoadNotification.java :
public class SendRoadNotification extends
ActionImpl<SendRoadNotificationResult> {
private RoadNotification roadNot;
#SuppressWarnings("unused")
private SendRoadNotification() {
// For serialization only
}
public SendRoadNotification(RoadNotification roadNot) {
this.roadNot = roadNot;
}
public RoadNotification getRoadNot() {
return roadNot;
}
}
ActionResult (on the client ) -- SendRoadNotfifcationResult.java :
public class SendRoadNotificationResult implements Result {
private RoadNotification roadNot;
#SuppressWarnings("unused")
private SendRoadNotificationResult() {
// For serialization only
}
public SendRoadNotificationResult(RoadNotification roadNot) {
this.roadNot = roadNot;
}
public RoadNotification getRoadNot() {
return roadNot;
}
}
ActionHandler ( on the server ) -- SendRoadNotificationActionHandler.java :
public class SendRoadNotificationActionHandler implements
ActionHandler<SendRoadNotification, SendRoadNotificationResult> {
static DataStore ds = DataStore.getDatastoreService();
#Inject
public SendRoadNotificationActionHandler() {
}
#Override
public SendRoadNotificationResult execute(SendRoadNotification action,
ExecutionContext context) throws ActionException {
//Here I am doing something with that action
}
#Override
public void undo(SendRoadNotification action,
SendRoadNotificationResult result, ExecutionContext context)
throws ActionException {
}
#Override
public Class<SendRoadNotification> getActionType() {
return SendRoadNotification.class;
}
}
The way I use those, is:
SendRoadNotification action = new SendRoadNotification(rn);
dispatchAsync.execute(action, sendRoadNotifCallback);
And the callback:
AsyncCallback<SendRoadNotificationResult> sendRoadNotifCallback = new AsyncCallback<SendRoadNotificationResult>() {
#Override
public void onSuccess(SendRoadNotificationResult result) {
}
#Override
public void onFailure(Throwable caught) {
Window.alert("Something went wrong");
}
};
How can I implement this in android ? Can somebody give me an example or had this problem before ?
I am using AppEngine sdk 1.6.4, GWT sdk 2.4.0, GWTP plugin for Eclipse and GPE plugin for Eclipse.
You might want to look at the source the GAE plugin for ADT generates for 'App Engine Connected Android apps' for inspiration. They are doing something similar by calling GWT endpoints using Android's HttpClient.
https://developers.google.com/eclipse/docs/appengine_connected_android