I want to know about Android Annotations, is it better way to use in all android projects?.
If correct, how to implement it. Is there any good tutorial for it?
If it is a wrong way. what are the drawbacks of Android Annotations?
Thanks in advance for the help.
Android Annotations is an annotation-driven framework that allows you to simplify the code in your applications and reduces the boilerplate of common patterns, such as setting click listeners, enforcing ui/background thread executions, etc.
You could go from having something like this:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView descriptionTextView = (TextView) findViewById(R.id.tv_description);
final Button hideButton = (Button) findViewById(R.id.btn_hide);
hideButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
descriptionTextView.setVisibility(View.INVISIBLE);
}
});
}
}
To something like this:
#EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
#ViewById(R.id.tv_description)
TextView mDescriptionTextView;
#Click(R.id.btn_hide)
protected void onHideButtonClick() {
mDescriptionTextView.setVisibility(View.INVISIBLE);
}
}
How it works
You annotate your activities and components, the annotations processor then generates classes (at compilation time) that extend your activities and components (i.e. your activities cannot be final) with an underscore suffix by default, so if you have MainActivity, now you will also have a MainActivity_ class.
This new class contains a well-written boilerplate code that does whatever the annotation specifies.
How to implement
I wrote this tutorial about how to integrate Android Annotations and even include an example on how integration tests get updated, check here.
That tutorial is valid as of today, using Android Studio ~1.5.1, and it tries to explain a little bit on how the internal works.
Should you use it?
I would say that if you have a small-medium project its fine. It will make your code easier to read. But if your application is bigger and contains a lot of navigation flows with complex activity/component life cycles, it can get a little bit hard to implement or difficult to debug and understand errors if something is not appropriately annotated.
Because of how Android Annotations operate, they embed themselves in the life cycle and doing so, you are now dependent of their lifecycle (e.g. if you annotate your views with #ViewById, then you cannot reference them in onCreate(), you need to make a method and annotate it with #AfterViews and when this method gets executed then your views are ready to be used). This is not necessarily a problem, you just need to have a good understanding of Android's behaviors and well, Android Annotations behaviors as well.
In summary, as in any library, if you depend on it, well you depend on it, so you might as well understand very thoroughly how it works. Your project now depends on someone else's.
I don't use Android Annotations, not anymore. When I used this library, it was buggy and made debugging a nightmare. Another downside is that it lowers the portability of your code. If you're working alone on the project, then it's okay, you don't have this issue, but when you work in a team, you have to give this a second thought.
If you want to use it, there are plenty of tutorials right on their site.
An alternative:
If you want to decrease the amount of code while making it really easy to use and understand, I suggest you the Butter Knife library. I use is a lot and didn't encounter any bugs so far. Very easy to use and read.
Android Annotations is a library that "autogenerates" code for us by using some attributes or anotations like #EActivity, #ViewById, #OnClick. It's intended to facilitate and decrease coding time.
"AndroidAnnotations is an Open Source framework that speeds up Android development. It takes care of the plumbing, and lets you concentrate on what's really important. By simplifying your code, it facilitates its maintenance."
(Documentation here: https://github.com/excilys/androidannotations/wiki)
But... We don't use it, I completely agree with DDsix answer. Use SOLID principles and code what should be coded when and where it should be...
Android Annotations is 3rd party library that was made to be all in one tool for Android. Its capable of dependency injection, thread handling, and more. I don't recommend using it; it's buggy, and unstable. In my current job I am working on a project and my task is to remove Android Annotations. I would suggest using Dagger 2, Butterknife, and RxJava
Here is the example with Android Studio.
Create an Android project with the "Empty Activity" template.
Edit the project_root/build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.5.0'
==> classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
allprojects {
repositories {
jcenter()
==> maven {
==> url = 'https://oss.sonatype.org/content/repositories/snapshots'
==> }
}
}
Edit the app/build.gradle
apply plugin: 'com.android.application'
apply plugin: 'android-apt' <============
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
applicationId "com.just.myapplication"
minSdkVersion 19
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1'
==> apt "org.androidannotations:androidannotations:4.0-SNAPSHOT"
==> compile 'org.androidannotations:androidannotations-api:4.0-SNAPSHOT'
}
Add a TextView and a Button in the main layout, activity_main.xml
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Not Click Yet"
android:id="#+id/textView"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:id="#+id/button"
android:text="Click"
android:layout_below="#+id/textView" />
Change the activity name to "MainActivity_" in AndroidManifest.xml
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme">
==> <activity android:name=".MainActivity_">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
Now, your MainActivity.java could be simplified as below
package com.just.myapplication;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.ViewById;
#EActivity (R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
#ViewById(R.id.textView)
TextView mText;
#Click
void button() {
mText.setText("Button Clicked!");
}
}
Try to run it with a device or a emulator to see how simple it works.
Related
Background
Suppose I make an Android library called "MySdk", and I publish it on Jitpack/Maven.
The user of the SDK would use it by adding just the dependency of :
implementation 'com.github.my-sdk:MySdk:1.0.1'
What I'd like to get is the "1.0.1" part from it, whether I do it from within the Android library itself (can be useful to send to the SDK-server which version is used), or from the app that uses it (can be useful to report about specific issues, including via Crashlytics).
The problem
I can't find any reflection or gradle task to reach it.
What I've tried
Searching about it, if I indeed work on the Android library (that is used as a dependency), all I've found is that I can manage the version myself, via code.
Some said I could use BuildConfig of the package name of the library, but then it means that if I forget to update the code a moment before I publish the dependency, it will use the wrong value. Example of using this method:
plugins {
...
}
final def sdkVersion = "1.0.22"
android {
...
buildTypes {
release {
...
buildConfigField "String", "SDK_VERSION", "\"" + sdkVersion + "\""
}
debug {
buildConfigField "String", "SDK_VERSION", "\"" + sdkVersion + "-unreleased\""
}
}
Usage is just checking the value of BuildConfig.SDK_VERSION (after building).
Another possible solution is perhaps from gradle task inside the Android-library, that would be forced to be launched whenever you build the app that uses this library. However, I've failed to find how do it (found something here)
The question
Is it possible to query the dependency version from within the Android library of the dependency (and from the app that uses it, of course), so that I could use it during runtime?
Something automatic, that won't require me to update it before publishing ?
Maybe using Gradle task that is defined in the library, and forced to be used when building the app that uses the library?
You can use a Gradle task to capture the version of the library as presented in the build.gradle dependencies and store the version information in BuildConfig.java for each build type.
The task below captures the version of the "appcompat" dependency as an example.
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.0'
}
task CaptureLibraryVersion {
def libDef = project.configurations.getByName('implementation').allDependencies.matching {
it.group.equals("androidx.appcompat") && it.name.equals("appcompat")
}
if (libDef.size() > 0) {
android.buildTypes.each {
it.buildConfigField 'String', 'LIB_VERSION', "\"${libDef[0].version}\""
}
}
}
For my example, the "appcompat" version was 1.4.0. After the task is run, BuildConfig.java contains
// Field from build type: debug
public static final String LIB_VERSION = "1.4.0";
You can reference this field in code with BuildConfig.LIB_VERSION. The task can be automatically run during each build cycle.
The simple answer to your question is 'yes' - you can do it. But if you want a simple solution to do it so the answer transforms to 'no' - there is no simple solution.
The libraries are in the classpath of your package, thus the only way to access their info at the runtime would be to record needed information during the compilation time and expose it to your application at the runtime.
There are two major 'correct' ways and you kinda have described them in your question but I will elaborate a bit.
The most correct way and relatively easy way is to expose all those variables as BuildConfig or String res values via gradle pretty much as described here. You can try to generify the approach for this using local-prefs(or helper gradle file) to store versions and use them everywhere it is needed. More info here, here, and here
The second correct, but much more complicated way is to write a gradle plugin or at least some set of tasks for collecting needed values during compile-time and providing an interface(usually via your app assets or res) for your app to access them during runtime. A pretty similar thing is already implemented for google libraries in Google Play services Plugins so it would be a good place to start.
All the other possible implementations are variations of the described two or their combination.
You can create buildSrc folder and manage dependencies in there.
after that, you can import & use Versions class in anywhere of your app.
I am working one project using kotlin + Rxjava + MVVM. During development facing issue of importing view ids in Fragment or viewholder.
import kotlinx.android.synthetic.main.layout.* unused with kotlin.
Normaly view id should used from kotlin synthetic layout imports but it directly import it from R.id that should not happen.
Kotlin plugin version : org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.40
My gradle file :
apply plugin: 'com.android.feature'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'idea'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 27
baseFeature true
defaultConfig {
minSdkVersion 23
targetSdkVersion 27
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
}
dependencies {
api "com.android.support:design:$rootProject.support_library_version"
api "com.android.support:appcompat-v7:$rootProject.support_library_version"
api "com.android.support:recyclerview-v7:$rootProject.support_library_version"
api "com.android.support:support-dynamic-animation:$rootProject.support_library_version"
api "com.android.support:cardview-v7:$rootProject.support_library_version"
api "com.android.support:customtabs:$rootProject.support_library_version"
api "com.android.support.constraint:constraint-layout:1.1.0-beta5"
api 'android.arch.lifecycle:extensions:1.1.0'
api 'androidx.core:core-ktx:0.2'
api "com.google.dagger:dagger:$rootProject.dagger_version"
kapt "com.google.dagger:dagger-compiler:$rootProject.dagger_version"
api "android.arch.persistence.room:runtime:$rootProject.room_version"
kapt "android.arch.persistence.room:compiler:$rootProject.room_version"
testImplementation "android.arch.persistence.room:testing:$rootProject.room_version"
api "android.arch.persistence.room:rxjava2:$rootProject.room_version"
androidTestImplementation "android.arch.core:core-testing:$rootProject.room_version"
testImplementation "android.arch.core:core-testing:$rootProject.room_version"
api "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
api 'com.jakewharton.timber:timber:4.5.1'
api "com.android.support:multidex:1.0.3"
api "com.github.bumptech.glide:glide:$rootProject.glide_version"
api "jp.wasabeef:glide-transformations:$rootProject.glide_transformation_version"
api 'com.github.bumptech.glide:okhttp3-integration:1.5.0#aar'
api "io.reactivex.rxjava2:rxandroid:$rootProject.rxAndroid_version"
api "io.reactivex.rxjava2:rxjava:$rootProject.rxJava_version"
api "com.google.code.gson:gson:$rootProject.gson_version"
api("com.squareup.retrofit2:retrofit:$rootProject.retrofit_version") {
// exclude Retrofit’s OkHttp peer-dependency module and define your own module import
exclude module: 'okhttp'
}
api "com.squareup.okhttp3:okhttp:$rootProject.okhttp_version"
api "com.squareup.okhttp3:logging-interceptor:$rootProject.okhttp_version"
api "com.squareup.retrofit2:adapter-rxjava2:$rootProject.retrofit_version"
api "com.squareup.retrofit2:converter-gson:$rootProject.retrofit_version"
api 'com.jakewharton.threetenabp:threetenabp:1.0.5'
api "com.google.firebase:firebase-invites:$rootProject.play_services_version"
api "com.google.firebase:firebase-core:$rootProject.play_services_version"
api "com.google.firebase:firebase-config:$rootProject.play_services_version"
api "com.google.firebase:firebase-perf:$rootProject.play_services_version"
api "com.google.firebase:firebase-auth:$rootProject.play_services_version"
api "com.google.firebase:firebase-firestore:$rootProject.play_services_version"
api("com.firebaseui:firebase-ui-auth:$rootProject.firebase_ui_version") {
// exclude Retrofit’s OkHttp peer-dependency module and define your own module import
exclude module: 'play-services-auth'
exclude module: 'firebase-auth'
}
// Required only if Facebook login support is required
api('com.facebook.android:facebook-android-sdk:4.31.0')
api "com.google.android.gms:play-services-auth:$rootProject.play_services_version"
// Required only if Twitter login support is required
api("com.twitter.sdk.android:twitter-core:3.0.0#aar") { transitive = true }
api 'com.jakewharton.rxbinding2:rxbinding-kotlin:2.0.0'
api 'com.jakewharton.rxbinding2:rxbinding-support-v4-kotlin:2.0.0'
api 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7-kotlin:2.0.0'
api 'com.jakewharton.rxbinding2:rxbinding-design-kotlin:2.0.0'
api('com.crashlytics.sdk.android:crashlytics:2.9.1#aar') {
transitive = true
}
}
I have also tried clean build and Rebuild project.
Any idea how can i resolve this issue ?
I have tried several approaches including the solutions reported in this thread. I also found out that a lot of folks are facing this annoying problem as you can see here
Nevertheless, the most closest solution to this problem which has worked for me so far is removing apply plugin: kotlin-android-extensions from gradle, Sync gradle plugin and then add it again.
I'm using Android Studio 3.1.3 and I encountered the same issue. I managed to solve this by moving all my codes from java/ to kotlin/ directory inside main/.
app/
|-- src/
| |-- main/
| | |-- java/
| | | |-- com.example.android.app
| | |-- kotlin/ <-- (use this)
| | | |-- com.example.android.app
Then, add the kotlin/ as part of the source sets:
app/build.gradle
android {
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
}
Sometimes, it still requires to sync and rebuild the project to properly import the kotlinx.android....
Reference: Add kotlin code
I have the same problem and I am trying to solve it for too many days...
One trick you can do is to Exclude from Import and Completion <package-name>.R.id.* for project scope.
Go to Settings/Editor/Auto Import to add it.
It improves our issue and if you do this and clean the project, it will work but it does not resolve the issue completely. Many times the imports reappear as unused imports and there is to clean the project over and over :-(.
EDITED
Also, another improvement I have achieved is working with includes on XML. For example, if I am going to use "the same" button in several screens, I make a specific layout for this button and I re-use it on several activities / fragments. You can set the id within this specific layout and synthetic will auto-import it without generating conflicts, due to you have the content view reference declared before.
I show you a simple example:
activity_main.xml
<!-- ... -->
<include layout="#layout/btn_foo"/>
<!-- ... -->
btn_foo.xml
<?xml version="1.0" encoding="utf-8"?>
<Button
android:id="#+id/btnFoo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"/>
MainActivity.kt
// ...
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.btn_foo.*
// ...
setContentView(R.layout.activity_main)
// ...
btnFoo.setOnClickListener { }
I have to admit that in the other cases I have returned to the typical Hungarian convention whatWhereDescription(Size) to set the ids due to is too much annoying to deal with imports among activities / fragments / view all the time.
I've solved similar issues for ViewHolder implementations:
We have to inherit our ViewHolder class implementation from LayoutContainer. LayoutContainer is an interface available in kotlinx.android.extensions package.
You will have some code similar with this:
class TaskVH(override val containerView: View, val itemListener: TasksFragment.TaskItemListener) : RecyclerView.ViewHolder(containerView), LayoutContainer {
fun bindItem(task: Task) {
item_title.text = ""
item_complete.isChecked = task.isCompleted
itemView.setBackgroundResource(rowViewBackground)
itemView.setOnClickListener { itemListener.onTaskClick(task) }
}
}
I don't know if this tripped anyone else up, but I was having problems because I didn't realize the synthetic objects are only available if you're inside an Activity, Dialog, or Fragment. If you're in some other class (like using Conductor's Controller) you're out of luck.
Cross-posting my bug report with workaround from here: https://issuetracker.google.com/issues/145888144
Original bug was closed as "fixed" by google, but it is definitely not fixed: https://issuetracker.google.com/issues/78547457
SUMMARY:
For some modules in a multi-module project, the IDE does not properly recognize imports for "synthetic" symbols that abstract view ids, a feature provided by the plugin 'kotlin-android-extensions'. This results in class files using these symbols appearing full of errors, as the imports are "unresolved", the symbols are then unknown, and of course anything using these symbols fail because the types are unknown.This is solely an IDE problem though; everything compiles normally and works as expected.
ROOT CAUSE
The kotlin-language facet is not being applied to the errant modules.
BACKGROUND
Facets in IntelliJ IDEA projects are configurable on a module-by-module basis in Project settings. In Android Studio, this part of the Project settings UI is missing (or suppressed.) Presumably, Android Studio is attempting to apply the correct facets based on the gradle plugins applied to each module. This process is /sometimes/ FAILING for this particular case; I have not been able to determine what triggers the success/failure of the process.
TEMPORARY WORKAROUND
This workaround works consistently, and has since 3.5.2 when I discovered it:
Open the ".iml" file for the module you are trying to "fix" in Android Studio editor. (In AS > 4, the iml file is in .idea/modules, earlier than that it was in top-level of your module.) Find the <component name="FacetManager"> block, and notice that there is no facet called "kotlin-language" there. Paste the contents of the text block below so it appears within the <component name="FacetManager"> block. Save the file.
Re-import gradle.
This is a clumsy workaround, and has to be applied on a module-by-module basis. What's more, it may be required to sometimes re-apply the workaround after making changes to the build.gradle file for an affected module. The hope is that Google (or JetBrains, if it turns out it is their problem) will fix this problem properly.
Given that kotlin-android-extensions has fallen out of favor, I don't expect this bug will ever be fixed.
Text to use for the fix
<facet type="kotlin-language" name="Kotlin">
<configuration version="3" platform="JVM 1.8" allPlatforms="JVM [1.8]" useProjectSettings="false">
<compilerSettings />
<compilerArguments>
<option name="jvmTarget" value="1.8" />
<option name="pluginOptions">
<array>
<option value="plugin:org.jetbrains.kotlin.android:enabled=true" />
<option value="plugin:org.jetbrains.kotlin.android:defaultCacheImplementation=hashMap" />
</array>
</option>
</compilerArguments>
</configuration>
</facet>
There is an existing issue (which is assigned) on Google tracker regarding synthetic imports.
https://issuetracker.google.com/issues/78547457
For Conductor:
Create this base class.
import android.os.Bundle
import android.view.View
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.RestoreViewOnCreateController
abstract class BaseController(
bundle: Bundle? = null
) : RestoreViewOnCreateController(bundle){
init {
addLifecycleListener(object : LifecycleListener() {
override fun postCreateView(controller: Controller, view: View) {
onViewCreated(view)
}
})
}
open fun onViewCreated(view: View) { }
}
Then in your controller:
import kotlinx.android.synthetic.main.controller_example.view.*
class ProfileController : BaseController() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View {
return inflater.inflate(R.layout.controller_example, container, false)
}
override fun onViewCreated(view: View) {
view.txtName.text = "Example"
}
}
I try every other solution bu no one worked for me. At the and I cloned project again and now it's working. I love android studio
Add id kotlin-android-extensions in your build.gradle file in the plugins block.
Google removed this feature by default
I am currently considering porting a app that I start developping with react-native to codenameone. For this, I am still checking the feasability and the amount of work it would requiere (as I would have to port or developp some native library binding from react-native to codenameone because codenameone miss some of my needs, like socket.io support for example). The free codenameone build cloud service beeing limited to app of 1Mb, I have to make my test builds locally (with only a few test classes and the use of the google maps cn1lib, my test app is already above the 1Mb limit)
Sadly, there is no free documentation on codenameone on how to perform local builds and actually I couldn't find any instructions on internet on how to do it (I only found, on a blog post, some basic and deprecated instructions on how to perform a local iOS build but nothing for Android). So I had to figure it out myself...
After some time spent digging into gradle configuration parametters, I finally succeed into building a basic codenameone app localy that works on my android test device. But the problem is that, when I add an external cn1lib (the google maps native cn1lib https://github.com/codenameone/codenameone-google-maps ), my app bug when oppening a screen that depends from this lib.
In the android error log, I could find this message:
D/MyApplication( 551): [EDT] 0:0:0,99 - Exception: java.lang.ClassCastException - com.codename1.googlemaps.InternalNativeMapsImpl cannot be cast to com.codename1.system.NativeInterface
W/System.err( 551): java.lang.ClassCastException: com.codename1.googlemaps.InternalNativeMapsImpl cannot be cast to com.codename1.system.NativeInterface
W/System.err( 551): at com.codename1.system.NativeLookup.create(Unknown Source)
W/System.err( 551): at com.codename1.googlemaps.MapContainer.<init>(MapContainer.java:171)
W/System.err( 551): at com.codename1.googlemaps.MapContainer.<init>(MapContainer.java:151)
W/System.err( 551): at com.tbdlab.testapp.MyApplication.start(MyApplication.java:207)
W/System.err( 551): at com.tbdlab.testapp.MyApplicationStub.run(MyApplicationStub.java:183)
W/System.err( 551): at com.codename1.ui.Display.processSerialCalls(Unknown Source)
W/System.err( 551): at com.codename1.ui.Display.mainEDTLoop(Unknown Source)
W/System.err( 551): at com.codename1.ui.RunnableWrapper.run(Unknown Source)
W/System.err( 551): at com.codename1.impl.CodenameOneThread$1.run(Unknown Source)
W/System.err( 551): at java.lang.Thread.run(Thread.java:818)
I don't really understand why InternalNativeMapsImpl could not be cast into NativeInterface as I looked into the dex file of my compiled apk and all the necessary classes (for android) from the google maps cn1lib are correctly included (So I have com.codenameone.googlemaps.InternalNativeMaps, com.codenameone.googlemaps.InternalNativeMapsImpl and com.codenameone.googlemaps.MapContainer) and so are the codenameone native interface classes they depend on (com.codename1.system.NativeInterface, com.codename1.impl.android.LifecycleListener...). And I decompilled them and the code is correct (I do not use any obfuscation method anyway so there is no real reason why the compiled code would have differ from the source code). There is probably something that I am missing here to make a local codenameone build with the usage of a cn1lib.
So has anyone already succeed into making a local build with the usage of a cn1lib that perform native bindings? If yes, what is the exact procedure?
I really hope someone would be able to help, because, at this point, I am seriously considering to stick with react-native (which I am quite pleased with, exept the fact that it is not completely native) or to jump into flutter (or kotlin native) even if I still think codenameone offers many advantages over these other solutions (but not beeing able to perform local builds during the development phase is just a complete no-go for me)
As said, in some of my tests (where I use the full set of cn1libs I would need + some custom libs), I am already above the 1Mb limitation (the server rejected my test builds for this reason). So using the free build cloud server during the development phase is not an option for me (Anyway I won't use a solution if I am not sure I can be completely independent if necessary. To make my release builds I would certainly take a subscription and use the cloud buid server as it is far more convenient than tweeking a local server, furthermore that I don't own a Mac computer (I only have a test iphone) and need to borrow one when I want to make some iOS build ;) . But I want to be sure that if, for any reason, your service dissapear, I will still be able to make my builds. Furthermore, I don't see the point of paying a subscription during the developpment phase of my app (that could take me months) especially as I am not certain I would use codenameone as a final solution (I still have to check the amount of work it would requiere to adapt some of the libs I already have for react-native to codenameone)). That is the reason why I try to make a local build.
Concerning the socket.io library, I already started to create a cn1lib that will use native solutions (https://github.com/socketio/socket.io-client-java for android, https://github.com/socketio/socket.io-client-swift for iOS, and the original socket.io lib for javascript). This is not really an issue and it was just to give an example of libraries I would have to create in codenameone if I want to switch from react-native.
In what concerns how cn1lib works, I already figured that out, I included into my android project all the necessary class of the cn1 google-maps lib (so I included the content from main.zip, nativeand.zip and stubs.zip in my project) and checked in the .dex files of my generated apk that they are actually correctly packaged in them, as already said. So my problem doesn't seems to be that I forgot to include some class of the cn1lib in my project but something else. The error message is: Exception: java.lang.ClassCastException - com.codename1.googlemaps.InternalNativeMapsImpl cannot be cast to com.codename1.system.NativeInterface so it doesn't refer to a Class not found but to a cast exception... I don't really know what can cause this issue. I took the codenameone core classes from here https://github.com/codenameone/CodenameOne/tree/master/CodenameOne/src/
https://github.com/codenameone/CodenameOne/tree/master/Ports/Android http://github.com/codenameone/codenameone-skins
to include them in my project, so I think I didn't miss one. And when building a project that doesn't use a cn1lib (like a simple "hello word" app), it compiles and run just fine on my android test device.
The problem, is really just when my app try to create a googlemap view, where it returns the cast exception (and then default to try to create an html browser mapview and fails here as it is missing some html file).
So it is probably a configuration problem ( may it be a problem with the java version used by the compiler as native class files where already compiled in the cn1lib main.zip file?)
Here is the gradle build file I use:
buildscript {
repositories {
jcenter()
maven { url "https://plugins.gradle.org/m2/" }
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'me.tatarka:gradle-retrolambda:3.2.0'
}
}
allprojects {
repositories {
jcenter()
google()
}
}
apply plugin: 'com.android.application'
apply plugin: 'me.tatarka.retrolambda'
android {
compileSdkVersion 26
buildToolsVersion '26.0.2'
dexOptions {
// Prevent OutOfMemory with MultiDex during the build phase
javaMaxHeapSize "4g"
}
lintOptions {
checkReleaseBuilds false
}
defaultConfig {
applicationId "com.tbdlab.testapp"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
multiDexEnabled true
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' //my proguard files are actually empty so no obfuscation is performed. I checked it in the generated apk
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7//.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_7//.VERSION_1_8
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.google.android.gms:play-services:9.4.0' //compile 'com.google.android.gms:play-services-maps:11.8.0'
compile 'com.android.support:multidex:1.0.1'
}
and here is my AndroidManifest.xml file where I included all the permissions defined in the cn1lib:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!--- Permissions requiered by the google maps cn1lib -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
<uses-feature android:glEsVersion="0x00020000" android:required="true"/>
<application android:allowBackup="true" android:icon="#drawable/icon" android:label="MyApplication" android:name="android.support.multidex.MultiDexApplication">
<meta-data android:name="com.google.android.gms.version" android:value="#integer/google_play_services_version"/>
<meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="...masked_it_but_put_my_correct_key_here..."/>
<activity android:label="MyApplication" android:launchMode="singleTop" android:name="com.tbdlab.testapp.MyApplicationStub" >
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<receiver android:name="com.codename1.impl.android.LocalNotificationPublisher"/>
<service android:exported="false" android:name="com.codename1.impl.android.BackgroundFetchHandler"/>
<activity android:name="com.codename1.impl.android.CodenameOneBackgroundFetchActivity" android:theme="#android:style/Theme.NoDisplay"/>
<activity android:name="com.codename1.location.CodenameOneBackgroundLocationActivity" android:theme="#android:style/Theme.NoDisplay"/>
<service android:exported="false" android:name="com.codename1.location.BackgroundLocationHandler"/>
<service android:exported="false" android:name="com.codename1.location.GeofenceHandler"/>
<service android:exported="false" android:name="com.codename1.media.AudioService"/>
<activity android:excludeFromRecents="true" android:exported="false" android:name="com.google.android.gms.auth.api.signin.internal.SignInHubActivity" android:theme="#android:style/Theme.Translucent.NoTitleBar"/>
<provider android:authorities="com.tbdlab.testapp.google_measurement_service" android:exported="false" android:name="com.google.android.gms.measurement.AppMeasurementContentProvider"/>
<receiver android:enabled="true" android:name="com.google.android.gms.measurement.AppMeasurementReceiver">
<intent-filter>
<action android:name="com.google.android.gms.measurement.UPLOAD"/>
</intent-filter>
</receiver>
<service android:enabled="true" android:exported="false" android:name="com.google.android.gms.measurement.AppMeasurementService"/>
<activity android:name="com.google.android.gms.ads.AdActivity" android:theme="#android:style/Theme.Translucent"/>
I really don't see what can cause the cast exception, but in the lack of a basic tutorial on how to create local builds, I may have miss something without even knowing it...
For this test I made a really simple app than only display a native google map and it runs correctly in the simulator and compiles on the build cloud server and runs fine in my android test device. So the issue is either in my gradle build configuration (or maybe AndroidManifest.xml file even if I don't think it has any effect on the JVM) or in the codenameone core and cn1lib I included in my android project for the local build.
1mb is huge as it can fit the full google maps app and a lot more. It maps to the compiled size of the jar which starts off at 6kb. The whole cn1lib (only a portion of it is packaged) is 40kb. So I would suggest using the build servers for your tests.
Steve built some support for working with native interfaces a few years back here. He stopped maintaining it a bit after we hired him mostly due to lack of time and demand (not because we told him or anything like that). I'm not sure about the status of this but you can use it as a reference to how native interfaces work.
There is also this plugin (direct link here) which I personally didn't try.
Generally a native interface generates an intermediate class that invokes the native implementation directly. The native implementation for all platforms other than Java SE doesn't implement the native interface and shouldn't. I think I explained it somewhere in the docs but explaining it again in the case of Google Maps is super easy.
This is a method from the native interface:
public PeerComponent createNativeMap(int mapId);
This is the same method from the Android implementation class:
public android.view.View createNativeMap(int mapId);
As you can see the return value differs and we need to wrap it in a peer component to abstract that behavior. By avoiding inheritance and casting we get the flexibility of making a more sensible native API.
Here is the class our build server generates for maps, as you can see it's just "glue code":
package com.codename1.googlemaps;
import com.codename1.ui.PeerComponent;
public class InternalNativeMapsStub implements InternalNativeMaps{
private InternalNativeMapsImpl impl = new InternalNativeMapsImpl();
public void setShowMyLocation(boolean param0) {
impl.setShowMyLocation(param0);
}
public void setRotateGestureEnabled(boolean param0) {
impl.setRotateGestureEnabled(param0);
}
public void setMapType(int param0) {
impl.setMapType(param0);
}
public int getMapType() {
return impl.getMapType();
}
public int getMaxZoom() {
return impl.getMaxZoom();
}
public int getMinZoom() {
return impl.getMinZoom();
}
public long addMarker(byte[] param0, double param1, double param2, String param3, String param4, boolean param5) {
return impl.addMarker(param0, param1, param2, param3, param4, param5);
}
public void addToPath(long param0, double param1, double param2) {
impl.addToPath(param0, param1, param2);
}
public long finishPath(long param0) {
return impl.finishPath(param0);
}
public void removeMapElement(long param0) {
impl.removeMapElement(param0);
}
public void removeAllMarkers() {
impl.removeAllMarkers();
}
public PeerComponent createNativeMap(int param0) {
return PeerComponent.create(impl.createNativeMap(param0));
}
public void setPosition(double param0, double param1) {
impl.setPosition(param0, param1);
}
public void calcScreenPosition(double param0, double param1) {
impl.calcScreenPosition(param0, param1);
}
public int getScreenX() {
return impl.getScreenX();
}
public int getScreenY() {
return impl.getScreenY();
}
public void calcLatLongPosition(int param0, int param1) {
impl.calcLatLongPosition(param0, param1);
}
public double getScreenLat() {
return impl.getScreenLat();
}
public double getScreenLon() {
return impl.getScreenLon();
}
public void deinitialize() {
impl.deinitialize();
}
public float getZoom() {
return impl.getZoom();
}
public void setZoom(double param0, double param1, float param2) {
impl.setZoom(param0, param1, param2);
}
public double getLatitude() {
return impl.getLatitude();
}
public double getLongitude() {
return impl.getLongitude();
}
public long beginPath() {
return impl.beginPath();
}
public void initialize() {
impl.initialize();
}
public boolean isSupported() {
return impl.isSupported();
}
}
About socket.io you can probably just wrap the JavaScript version with a call to the BrowserComponent to get the native JS code working as a start. A full on native port can come later.
It seems you have cn1libs figured out otherwise but just for completeness this is how they are supposed to work:
The cn1lib is just a zip file containing other zip files for each platform. Refresh libs unzips the this and arranges the files in the appropriate directories under lib/impl. So you need to package the lib/impl directory matching the platform you are trying to compile with your distribution.
cn1libs also include two additional property files codenameone_library_appended.properties & codenameone_library_required.properties. Refresh libs will handle that automatically for you by setting these values into the build hints. The former values are appended to the existing build hint and the latter override an existing build hint.
Build hints effectively tell the build servers how to compile some things e.g. if we want to inject stuff into the plist, manifest etc. How this maps to a local build will vary a lot. In some cases like plistInject it would be trivial to understand but other cases might be odd. If you have a question about how a specific build hint maps to local build then you can ask that.
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)
In the project I'm working we've recently added some level of security, now i don't want to have to rewrite the entire nework logic if it can be done much more easily with AOP.
So, I'm trying to intercept the "onRequestSuccess" method of the requestListeners that are used throughout the application.
For this I have made a simple aspect:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class NetworkResponseAspect {
#Around("execution(public void *.onRequestSuccess(..))")
public void intercept(ProceedingJoinPoint joinPoint){
System.out.println("call intercepted " + joinPoint);
try {
joinPoint.proceed();
} catch (Throwable e) {
System.out.println("wut");
}
}
}
I've added the aspectj weaver dependency: compile 'org.aspectj:aspectjweaver:1.8.6'
And it seems to work, at least the annotations are recognized by android studio.
I've placed a breakpoint on the "joinpoint.proceed()" call and started the application in debug mode.
But when I log in (an action that triggers one such listener) nothing happens. Am I missing something?
In Maven you would use AspectJ Maven Plugin (current version is 1.7), in Gradle something similar. This is what you need for compilation if you want to use CTW (compile-time weaving). Those plugins should already contain a dependency on aspectjtools.jar which contains the AspectJ compiler and other stuff. If you use CTW, you need aspectjrt.jar (AspectJ runtime) as a default-scoped (compile) or dependency because it is needed during runtime as well.
If you want to use load-time weaving (LTW), though, you need aspectjweaver.jar on your JVM command line via -javaagent:... because the weaving agent needs to hook into class-loading before your first application class is loaded. P.S.: The weaving agent also contains the AspectJ runtime classes, so you do not need an additional dependency on the runtime in this case.