I am developing an Android app with Clean Architecture.
So I separated the modules to 'app', 'data', 'domain'.
But what I want to do is using an android.utils.Log in the module 'data', 'domain'.
I cannot find it in the 'data', 'domain' modules.
Below is my 'domain' module's gradle file.
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
sourceCompatibility = "7"
targetCompatibility = "7"
Should I add something in here?
Or should I just use "System.out.println()" method?
First of all I have to confirm your intentions are good :)
You need to do the following to achieve what you want:
Define final Log class + interface defining log, both in your domain:
public final class Log {
private static LogInterface logInterface;
public static void d(String tag, String message) {
logInterface.d(tag, message);
}
public static void setLogInterface(LogInterface logInterface) {
Log.logInterface = logInterface;
}
public interface LogInterface {
void d(String tag, String message);
//...
}
}
Notice all of the above are pure java, nothing bound to android.
In any of your android modules, create and implement android logger:
public class AndroidLog implements Log.LogInterface {
public void d(String tag, String message) {
android.util.Log.d(tag, message);
}
}
Probably in the same module as p2, initialize (initialization likely should happen when app is created) :
Log.setLogInterface(new AndroidLogger());
Now you can use your domain's Log like this: Log.d(...) - all around in your pure java modules.
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
I'm trying to create a simple app with retrofit 2, dagger 2 and MVP, but I struggle with dependencies, actualy, this is the error I get after i try to rebuild the project Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
java.lang.StackOverflowError
and also in App class where I provide AppComponent: can not resolve symbol 'DaggerAppComponent'
I'll try to show you what my project looks like so someone can see the problem, First one is my AppModule which includes PresentationModule.class
#Module(includes = PresentationModule.class)
public class AppModule {
private App app;
public AppModule(App app) {
this.app = app;
}
#Provides
#Singleton
public App provideApp() {
return app;
}
}
Presentation Module looks like this:
#Module(includes = InteractorModule.class)
public class PresentationModule {
#Provides
JokePresenter providePresenter(JokeInteractor jokeInteractor) {
return new JokePresenterImpl(jokeInteractor);
}
}
And InteractorModule:
#Module(includes = {NetworkModule.class, PresentationModule.class})
public class InteractorModule {
#Provides
JokeInteractor provideJokeInteractor(RetrofitService service, JokePresenter presenter) {
return new JokeInteractorImpl(service, presenter);
}
}
This is JokePresenter that has a reference to view and interactor:
public class JokePresenterImpl implements JokePresenter {
private JokeView mJokeView;
private List<String> mData = new ArrayList<>();
private String mJoke;
private JokeInteractor jokeInteractor;
public JokePresenterImpl(JokeInteractor jokeInteractor) {
this.jokeInteractor = jokeInteractor;
}
#Override
public void setView(JokeView view) {
this.mJokeView = view;
}
#Override
public void getRandomJoke() {
mJokeView.showProgress();
jokeInteractor.getRandomJoke();
}
}
And JokeInteractor that has a RetrofitService and JokePresenter references:
public class JokeInteractorImpl implements JokeInteractor {
private RetrofitService retrofitService;
private JokePresenter presenter;
public JokeInteractorImpl(RetrofitService service, JokePresenter presenter) {
this.retrofitService = service;
this.presenter = presenter;
}
#Override
public void getRandomJoke() {
retrofitService.getRandomJoke()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<RandomJokeResponse>() {
#Override
public void onCompleted() {
presenter.onRandomJokeCompleted();
}
#Override
public void onError(Throwable e) {
presenter.onError(e.getMessage());
}
#Override
public void onNext(RandomJokeResponse randomJokeResponse) {
presenter.onNextRandomJoke(randomJokeResponse);
}
});
}
Gradle dependencies:
apt 'com.google.dagger:dagger-compiler:2.7'
compile 'com.google.dagger:dagger:2.7'
provided 'javax.annotation:jsr250-api:1.0'
//retrofit
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.okhttp:okhttp:2.5.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
//rx java
compile 'io.reactivex:rxjava:1.1.6'
compile 'io.reactivex:rxandroid:1.2.1'
//dagger2
compile 'com.google.dagger:dagger:2.0'
provided 'org.glassfish:javax.annotation:10.0-b28'
Can someone see the problem here?
Look at the implementation of:
#Module(includes = InteractorModule.class)
public class PresentationModule
and
#Module(includes = {NetworkModule.class, PresentationModule.class})
public class InteractorModule
You have a cyclic dependency. You need to rethink your design. I propose to decouple Interactor from Presenter.
A simple solution:
Change getRandomJoke() implementation to return Observable and subscribe to it inside Presenter. And remove presenter reference from Interactor.
Interactor:
public class JokeInteractorImpl implements JokeInteractor {
private RetrofitService retrofitService;
public JokeInteractorImpl(RetrofitService service) {
this.retrofitService = service;
}
#Override
public Observable getRandomJoke() {
return retrofitService.getRandomJoke()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
Presenter:
#Override
public void getRandomJoke() {
mJokeView.showProgress();
jokeInteractor.getRandomJoke()
.subscribe(new Observer<RandomJokeResponse>() {
#Override
public void onCompleted() {
onRandomJokeCompleted();
}
#Override
public void onError(Throwable e) {
onError(e.getMessage());
}
#Override
public void onNext(RandomJokeResponse randomJokeResponse) {
onNextRandomJoke(randomJokeResponse);
}
});
}
Make sure that dependencies added in your gradle
implementation 'com.google.dagger:dagger-android:2.15'
implementation 'com.google.dagger:dagger-android-support:2.13'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.16'
Make sure adding AndroidSupportInjectionModule::class inside your modules.
Like this:
#Component(
modules = [
AndroidSupportInjectionModule::class,
....//Other module classes
]
)
This is what worked for me (Solution) :-
If you have any other build issues related to Dagger apart from DaggerAppComponent import issue, then make sure to fix them first, if not we will just end up wasting time.
If you fix the other Dagger issues, & then build your project.
The DaggerAppComponent will get generated, & then all you have to do is import it.
Brief info regarding the issue that I faced :-
The mistake that I did was that, I had another Dagger build issue, but I ignored it & spent my time trying to fix 'DaggerAppComponent' import problem.
Wont be relevant to you but in my case, the DI module class annotated with #Module had a single abstract method, & hence the module class had to be made abstract as well. But later accidentally I added 2 other non-abstract methods within this same abstract class. So when I fixed this & re-build the project. DaggerAppComponent was available for import.
This is all what I have in my app level build.gradle file in relation to dagger :-
Within plugins block :
id 'kotlin-kapt'
Within dependencies block :
implementation "com.google.dagger:dagger:2.43.2"
kapt "com.google.dagger:dagger-compiler:2.43.2"
(Please note that "2.43.2" was the latest available version at the time of posting this comment)
My failed attempts (These answers didn't work for me) :-
Clean > Re-build > Invalidates caches & restart.
Switching to Project view, deleting build folder & re-building the project.
Trying out various dagger versions & extra dagger dependencies that I didnt require.
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 have created a demo Android Lib project and used dagger 2.0 with the following steps:
Added the following jars to /libs folder:
dagger-2.0.jar
dagger-compiler-2.0.jar
dagger-producers-2.0-beta.jar
guava-18.0.jar
javawriter-2.5.1.jar
javax.annotation-api-1.2.jar
javax.inject-1.jar
Project -> Properties -> Java Compiler -> Annotation Processing (Enabled annotation processing)
Project -> Properties -> Java Compiler -> Annotation Processing - Factory path: Added all the above mentioned jars.
Created the following classes:
public class Car {
private Engine engine;
#Inject
public Car(Engine engine) {
this.engine = engine;
}
public String carDetails(){
String engineName = this.engine.getName();
int engineNumber = this.engine.getNumber();
return "This car has the following details: \n" + engineName + "----" + engineNumber;
}
}
public interface Engine {
public String getName();
public int getNumber();
}
public class Toyota implements Engine{
#Override
public String getName() {
return "This is toyota engine";
}
#Override
public int getNumber() {
return 1234567890;
}
}
#Component(modules = EngineModule.class)
public interface EngineComponent {
void inject();
}
#Module
public class EngineModule {
public EngineModule(DemoApplication demoApplication) {
}
#Provides
Engine provideEngine(){
return new Toyota();
}
}
But inside /.apt-generated folder there are only two files:
Car_Factory.java EngineModule_ProvideEngineFactory.java
DaggerEngineComponent.java is not there for me to build the component.
Could someone please help?
I'm guessing the annotation processor is encountering an error and Eclipse is not showing you the log. If you have log output in the Output view, you may want to paste that into the question.
Specifically, I think it's erroring out on void inject(), which isn't a format descibed in the #Component docs. Those docs describe three types of methods:
Parameterless factory methods that return an injectable type Dagger creates and injects, like Engine createEngine(), or
Single-parameter void methods that receive an instance created elsewhere and apply method and field injection, like void injectEngine(Engine) or Engine injectEngine(Engine).
Subcomponent-returning methods that combine your Component's bindings with those from another module.
Because your void inject() doesn't match any of those formats, Dagger is likely erroring out and refusing to create a DaggerEngineComponent.
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