I am creating an Android app using Google App Engine. In order to use GCM (Google Cloud Messaging), I have created a GCM module in Android Studio. This module provides a sample code that registers devices in the Datastore.
All was working well yesterday, and although nothing changed, I have this error when I try to register my device :
java.lang.NoSuchMethodError: com.google.appengine.api.datastore.Cursor: method <init>()V not found
I don't know what exactly means the notation <init>()V, but I found the Cursor class to be generated by the Google plugin of Android Studio, here :
This is the decompiled code inside Cursor.class :
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.google.appengine.api.datastore;
import java.io.Serializable;
public final class Cursor implements Serializable {
private String webString;
public Cursor(String webString) {
this.webString = webString;
}
public String toWebSafeString() {
return this.webString;
}
public static Cursor fromWebSafeString(String encodedCursor) {
if(encodedCursor == null) {
throw new NullPointerException("encodedCursor must not be null");
} else {
return new Cursor(encodedCursor);
}
}
public boolean equals(Object o) {
if(this == o) {
return true;
} else if(o != null && this.getClass() == o.getClass()) {
Cursor cursor = (Cursor)o;
return this.webString.equals(cursor.webString);
} else {
return false;
}
}
public int hashCode() {
return this.webString.hashCode();
}
public String toString() {
return this.webString;
}
}
Finally, this is my build.gradle :
// If you would like more information on the gradle-appengine-plugin please refer to the github page
// https://github.com/GoogleCloudPlatform/gradle-appengine-plugin
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.google.appengine:gradle-appengine-plugin:1.9.18'
}
}
repositories {
jcenter();
}
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'appengine'
sourceCompatibility = 1.7
targetCompatibility = 1.7
dependencies {
appengineSdk 'com.google.appengine:appengine-java-sdk:1.9.18'
compile 'com.google.appengine:appengine-endpoints:1.9.18'
compile 'com.google.appengine:appengine-endpoints-deps:1.9.18'
compile 'javax.servlet:servlet-api:2.5'
compile 'com.googlecode.objectify:objectify:4.0b3'
compile 'com.ganyo:gcm-server:1.0.2'
}
appengine {
downloadSdk = true
appcfg {
oauth2 = true
}
endpoints {
getClientLibsOnBuild = true
getDiscoveryDocsOnBuild = true
}
}
Because I changed nothing in the concerned code, I really can't understand what happened and I found nothing useful on the web.
Thank you in advance for your help.
Edit : StackTrace from the backend log
It's looking for the empty constructor : method init()v not found
It appears this could be because Cursor.java is pulled from your <module>/build/classes/main (or <module>/build/exploded-app/WEB-INF/classes/main), when really it should just be pulled in from a library appengine-api-1.0-sdk-<version>.jar.
Have you added the source for a Cursor.java into your project src folder somehow? The App Engine build creates a runnable build at <module>/build/exploded-app and the cursor class is usually sourced from <module>/build/exploded-app/WEB-INF/lib/appengine-api-1.0-sdk-<version>.jar
Related
What is wrong with my configuration or code ?
I have this error highlighted
Cannot resolve method 'plant(timber.log.Timber.DebugTree)'
for the code
import timber.log.Timber;
public class AppClass extends Application {
#Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) { Timber.plant(new Timber.DebugTree()); }
}
}
but it builds and it executes. Still I think it means something, no ?
Configuration infos:
Android Studio Bumblebee | 2021.1.1
classpath 'com.android.tools.build:gradle:7.1.0'
Gradle: com.jakewharton.timber:timber:5.0.1#aar
ext.kotlin_version = '1.6.10'
sourceCompatibility JavaVersion.VERSION_1_8
Until issue fixed (as #n8yn8 noted in question comment) I solved it with downgrade to version 4.7.1:
implementation 'com.jakewharton.timber:timber:4.7.1'
In app level build.gradle file, set the following jakewharton timber version:
implementation 'com.jakewharton.timber:timber:4.7.1'
Then in your application class onCreate() Method:
For Kotlin:
if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())
} else {
Timber.plant(ReleaseTree())
}
For Java:
if (BuildConfig.DEBUG) {
Timber.plant(new DebugTree());
} else {
Timber.plant(new ReleaseTree());
}
Inner ReleaseTree() class Kotlin:
inner class ReleaseTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (priority == Log.VERBOSE || priority == Log.DEBUG) {
return
}
// log your crash to your favourite
// Sending crash report to Firebase CrashAnalytics
// FirebaseCrash.report(message);
// FirebaseCrash.report(new Exception(message));
}
}
Inner ReleaseTree() class Java:
class ReleaseTree extends Timber.Tree {
#Override
protected void log(int priority, String tag, String message, Throwable t) {
if (priority == Log.VERBOSE || priority == Log.DEBUG) {
return;
}
// log your crash to your favourite
// Sending crash report to Firebase CrashAnalytics
// FirebaseCrash.report(message);
// FirebaseCrash.report(new Exception(message));
}
}
For the workaround solution without downgrade dependency version and also no need to apply with another dependency by keep applying the one from JakeWharton, we can try to config Timber in Kotlin instead of Java class since the warning message only appear on Java class.
By doing so, you can try two options bellow:
Convert your custom application class from Java to Kotlin
Create another class in Kotlin and create new method to config Timber with sample bellow:
TimberUtils.kt
import timber.log.Timber
object TimberUtils {
#JvmStatic
fun configTimber() {
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
}
}
YourCustomJavaClass.java
#Override
public void onCreate() {
super.onCreate();
TimberUtils.configTimber();
}
Hope it can resolve your problem.
For those using sentry-timber
Just use
implementation "io.sentry:sentry-android:$sentry_version"
implementation "io.sentry:sentry-android-timber:$sentry_version"
Remove this dependency
implementation "com.jakewharton.timber:timber:$timber_version"
For me, this fix resolves the issue
A simplified child module build.gradle.kts:
plugins {
id("com.android.library")
kotlin("android")
}
android {
androidComponents.beforeVariants { it: com.android.build.api.variant.LibraryVariantBuilder ->
it.enabled = run {
// logic to calculate if
it.productFlavors[0].second == "flavor" && it.buildType == "debug"
}
}
}
Is it possible to extract function for calculation of enabled state of buildVariant?
fun calculateIsEnabled(lvb: com.android.build.api.variant.LibraryVariantBuilder): Boolean {
return lvb.productFlavors[0].second == "flavor" && lvb.buildType == "debug"
}
I tried to declare the function in the root build.gradle.kts but I don't know how to access it from submodule and if it is possible at all
I tried to declare it in buildSrc module, but com.android.build.api.variant.LibraryVariantBuilder is undefined here because the plugin com.android.library is not present here and I think it is not allowed and/or meaningless
So, the question is: where to declare a shared function that uses types defined in a gradle plugin and need to be accessible in all submodules of type android library?
After several tries I solved it:
buildSrc/build.gradle.kts
repositories {
google()
mavenCentral()
}
plugins {
`kotlin-dsl`
}
dependencies {
// important: dependency only in simple string format!
implementation("com.android.tools.build:gradle:7.2.0-alpha03")
}
buildSrc/src/main/kotlin/Flavors.kt
import com.android.build.api.variant.LibraryVariantBuilder
import com.android.build.api.variant.ApplicationVariantBuilder
private fun isFlavorEnabled(flavor1: String, buildType: String): Boolean {
return flavor1 == "flavor" && buildType == "debug"
}
fun isFlavorEnabled(lvb: LibraryVariantBuilder): Boolean {
// productFlavors are pairs of flavorType(dimension) - flavorName(selectedFlavor)
return lvb.run { isFlavorEnabled(productFlavors[0].second, buildType ?: "") }
}
fun isFlavorEnabled(avb: ApplicationVariantBuilder): Boolean {
return avb.run { isFlavorEnabled(productFlavors[0].second, buildType ?: "") }
}
In library/build.gradle.kts and app/build.gradle.kts
android {
androidComponents.beforeVariants {
it.enabled = isFlavorEnabled(it)
}
}
I can add a module dependency using either of
aar dependency
local project module
Right now I am able to accomplish same using below
if (foo(":awesomemodule")) {
implementation 'com.example.app:awesomemodule:1.0'
}
else {
implementation project(':awesomemodule')
}
Now I want to repeat this code for multiple dependencies and want to create a method for same.
I want something like this
customAddImplementation(':awesomemodule')
ext.customAddImplementation = { moduleName ->
if (foo(moduleName)) {
return implmentation('com.example.app' + moduleName + ':1.0')
} else {
return project(path: moduleName)
}
}
But this approach is not working, as implementation() definition is not found when i add my dependencies using customAddImplementation(':awesomemodule')
Something like this should work:
def customModulePath(String moduleName) {
if (foo(moduleName)) {
return "com.example.app:$moduleName:1.0")
} else {
return project(":$moduleName")
}
}
dependencies {
implementation(customModulePath('awesomemodule'))
}
And bonus point: since the function doesn't try to add the custom module path to the implementation dependencies, but simply returns its path, you can ruse it to add dependencies to any other configuration.
We're trying to use the org.robolectric:robolectric:3.0 dependency from our own internal Nexus repository. The issue is that Robolectric tries to load some dependencies at runtime from a public repository (as mentioned here), and ignores any repository overrides in the build.gradle.
Since we don't have access to that public location from our intranet, my tests timeout after trying to load that dependency:
[WARNING] Unable to get resource
'org.robolectric:android-all:jar:5.0.0_r2-robolectric-1' from
repository sonatype (https://oss.sonatype.org/content/groups/public/):
Error transferring file: Operation timed out
The bottom section of the Robolectric configuration documentation recommends adding this to your Gradle configuration to override the URL:
android {
testOptions {
unitTests.all {
systemProperty 'robolectric.dependency.repo.url', 'https://local-mirror/repo'
systemProperty 'robolectric.dependency.repo.id', 'local'
}
}
}
Unfortunately, I've tested that and I never see that system property being set. I've printed it out from inside my custom Robolectric runner (which extends RobolectricGradleTestRunner) and that system property remains set to null.
System.out.println("robolectric.dependency.repo.url: " + System.getProperty("robolectric.dependency.repo.url"));
I also tried to do something similar to this comment (but that method doesn't exist to override in RobolectricGradleTestRunner), and I also tried setting the system properties directly in my custom Robolectric runner, and that didn't seem to help.
#Config(constants = BuildConfig.class)
public class CustomRobolectricRunner extends RobolectricGradleTestRunner {
private static final String BUILD_OUTPUT = "build/intermediates";
public CustomRobolectricRunner(Class<?> testClass) throws InitializationError {
super(testClass);
System.setProperty("robolectric.dependency.repo.url", "https://nexus.myinternaldomain.com/content");
System.setProperty("robolectric.dependency.repo.id", "internal");
System.out.println("robolectric.dependency.repo.url: " + System.getProperty("robolectric.dependency.repo.url"));
}
The Robolectric source code does seem to confirm that these system properties exist.
While not a fix for using the properties directly, another way to get this to work is by overriding getJarResolver() in a RobolectricTestRunner subclass and pointing it at your artifact host:
public final class MyTestRunner extends RobolectricTestRunner {
public MyTestRunner(Class<?> testClass) throws InitializationError {
super(testClass);
}
#Override protected DependencyResolver getJarResolver() {
return new CustomDependencyResolver();
}
static final class CustomDependencyResolver implements DependencyResolver {
private final Project project = new Project();
#Override public URL[] getLocalArtifactUrls(DependencyJar... dependencies) {
DependenciesTask dependenciesTask = new DependenciesTask();
RemoteRepository repository = new RemoteRepository();
repository.setUrl("https://my-nexus.example.com/content/groups/public");
repository.setId("my-nexus");
dependenciesTask.addConfiguredRemoteRepository(repository);
dependenciesTask.setProject(project);
for (DependencyJar dependencyJar : dependencies) {
Dependency dependency = new Dependency();
dependency.setArtifactId(dependencyJar.getArtifactId());
dependency.setGroupId(dependencyJar.getGroupId());
dependency.setType(dependencyJar.getType());
dependency.setVersion(dependencyJar.getVersion());
if (dependencyJar.getClassifier() != null) {
dependency.setClassifier(dependencyJar.getClassifier());
}
dependenciesTask.addDependency(dependency);
}
dependenciesTask.execute();
#SuppressWarnings("unchecked")
Hashtable<String, String> artifacts = project.getProperties();
URL[] urls = new URL[dependencies.length];
for (int i = 0; i < urls.length; i++) {
try {
urls[i] = Util.url(artifacts.get(key(dependencies[i])));
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
return urls;
}
#Override public URL getLocalArtifactUrl(DependencyJar dependency) {
URL[] urls = getLocalArtifactUrls(dependency);
if (urls.length > 0) {
return urls[0];
}
return null;
}
private String key(DependencyJar dependency) {
String key =
dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + dependency.getType();
if (dependency.getClassifier() != null) {
key += ":" + dependency.getClassifier();
}
return key;
}
}
}
It should be noted that this relies on two internal classes of Robolectric so care should be taken when upgrading versions.
You can set properties mavenRepositoryId and mavenRepositoryUrl of RoboSettings which are used by MavenDependencyResolver.
Example:
public class CustomRobolectricRunner extends RobolectricGradleTestRunner {
static {
RoboSettings.setMavenRepositoryId("my-nexus");
RoboSettings.setMavenRepositoryUrl("https://my-nexus.example.com/content/groups/public");
}
...
}
As per the linked Github issue, one fix is to configure a settings.xml in your ~\.m2 folder:
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<mirrors>
<mirror>
<id>jcenter</id>
<name>JCenter Remote</name>
<mirrorOf>*</mirrorOf>
<url>https://www.example.com/artifactory/jcenter-remote/</url>
</mirror>
</mirrors>
</settings>
<mirrorOf>*</mirrorOf> seems necessary to force Maven to redirect all repository requests to the one remote. See here for more details about mirror settings in Maven.
I found that using a remote of Sonatype is not sufficient, you should use a remote of JCenter or Maven Central in order to obtain all of the transitive dependencies.
As of time of this writing, those previous answers are now obsolete. If you refer to the latest robolectric documentation you need to override the robolectric.dependency.repo.url property like so:
android {
testOptions {
unitTests.all {
systemProperty 'robolectric.dependency.repo.url', 'https://local-mirror/repo'
systemProperty 'robolectric.dependency.repo.id', 'local'
}
}
}
I want to run 2 asynchronous tasks, one followed by the other (sequentially). I have read something about ZIP or Flat, but I didn't understand it very well...
My purpose is to load the data from a Local SQLite, and when it finishes, it calls the query to the server (remote).
Can someone suggests me, a way to achieve that?
This is the RxJava Observable skeleton that I am using (single task):
// RxJava Observable
Observable.OnSubscribe<Object> onSubscribe = subscriber -> {
try {
// Do the query or long task...
subscriber.onNext(object);
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
};
// RxJava Observer
Subscriber<Object> subscriber = new Subscriber<Object>() {
#Override
public void onCompleted() {
// Handle the completion
}
#Override
public void onError(Throwable e) {
// Handle the error
}
#Override
public void onNext(Object result) {
// Handle the result
}
};
Observable.create(onSubscribe)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
The operator to do that would be merge, see http://reactivex.io/documentation/operators/merge.html.
My approach would be to create two observables, let's say observableLocal and observableRemote, and merge the output:
Observable<Object> observableLocal = Observable.create(...)
Observable<Object> observableRemote = Observable.create(...)
Observable.merge(observableLocal, observableRemote)
.subscribe(subscriber)
If you want to make sure that remote is run after local, you can use concat.
Lukas Batteau's answer is best if the queries are not dependent on one another. However, if it is necessary for you obtain the data from the local SQLite query before you run the remote query (for example you need the data for the remote query params or headers) then you can start with the local observable and then flatmap it to combine the two observables after you obtain the data from the local query:
Observable<Object> localObservable = Observable.create(...)
localObservable.flatMap(object ->
{
return Observable.zip(Observable.just(object), *create remote observable here*,
(localObservable, remoteObservable) ->
{
*combining function*
});
}).subscribe(subscriber);
The flatmap function allows you to transform the local observable into a combination of the local & remote observables via the zip function. And to reiterate, the advantage here is that the two observables are sequential, and the zip function will only run after both dependent observables run.
Furthermore, the zip function will allow you to combine observables even if the underlying objects have different types. In that case, you provide a combining function as the 3rd parameter. If the underlying data is the same type, replace the zip function with a merge.
You can try my solutions, there are several ways to resolve your problem.
To make sure it's working, I created a stand alone working example and use this API to test: https://jsonplaceholder.typicode.com/posts/1
private final Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/posts/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
private final RestPostsService restPostsService = retrofit.create(RestPostsService.class);
private Observable<Posts> getPostById(int id) {
return restPostsService.getPostsById(id);
}
RestPostService.java
package app.com.rxretrofit;
import retrofit2.http.GET;
import retrofit2.http.Path;
import rx.Observable;
/**
* -> Created by Think-Twice-Code-Once on 11/26/2017.
*/
public interface RestPostsService {
#GET("{id}")
Observable<Posts> getPostsById(#Path("id") int id);
}
Solution1: Use when call multiple tasks in sequences, the result of previous tasks is always the input of the next task
getPostById(1)
.concatMap(posts1 -> {
//get post 1 success
return getPostById(posts1.getId() + 1);
})
.concatMap(posts2 -> {
//get post 2 success
return getPostById(posts2.getId() + 1);
})
.concatMap(posts3 -> {
//get post 3success
return getPostById(posts3.getId() + 1);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(finalPosts -> {
//get post 4 success
Toast.makeText(this, "Final result: " + finalPosts.getId() + " - " + finalPosts.getTitle(),
Toast.LENGTH_LONG).show();
});
Solution2: Use when call multiple tasks in sequences, all results of previous tasks is the input of the final task (for example: after uploading avatar image and cover image, call api to create new user with these image URLs):
Observable
.zip(getPostById(1), getPostById(2), getPostById(3), (posts1, posts2, posts3) -> {
//this method defines how to zip all separate results into one
return posts1.getId() + posts2.getId() + posts3.getId();
})
.flatMap(finalPostId -> {
//after get all first three posts, get the final posts,
// the final posts-id is sum of these posts-id
return getPostById(finalPostId);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(finalPosts -> {
Toast.makeText(this, "Final posts: " + finalPosts.getId() + " - " + finalPosts.getTitle(),
Toast.LENGTH_SHORT).show();
});
AndroidManifest
<uses-permission android:name="android.permission.INTERNET"/>
root build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'me.tatarka:gradle-retrolambda:3.2.0'
classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
// Exclude the version that the android plugin depends on.
configurations.classpath.exclude group: 'com.android.tools.external.lombok'
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
app/build.gradle
apply plugin: 'me.tatarka.retrolambda'
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion "26.0.1"
defaultConfig {
applicationId "app.com.rxretrofit"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:26.+'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
provided 'org.projectlombok:lombok:1.16.6'
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.3.0'
compile 'io.reactivex:rxandroid:1.2.1'
}
model
package app.com.rxretrofit;
import com.google.gson.annotations.SerializedName;
/**
* -> Created by Think-Twice-Code-Once on 11/26/2017.
*/
public class Posts {
#SerializedName("userId")
private int userId;
#SerializedName("id")
private int id;
#SerializedName("title")
private String title;
#SerializedName("body")
private String body;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
By the way, use Rx + Retrofit + Dagger + MVP pattern is a great combine.