Using ProGuard / Dexguard with multiple Android Studio Modules - android

I have an Android Studio android project with two modules, Module A and Module B. I am building and testing these modules and then distributing them as .aar files. When the parent app that uses the .aars runs I am encountering an AbstractMethodError and I can't figure out why. I have included -keep flags for classes and interfaces in the dexguard-project.txt files of both modules in the hopes that it would work but to no avail. Here's more information about the project:
Module A contains a class called Util.class.
public class Util {
private static CustomObject getObjectFromDb(Context context) {
return new CustomObject();
}
public static class GetObjectTask extends AsyncTask<Context, Void, CustomObject> {
FetchCustomObjectListener mListener;
Context mContext;
public GetObjectTask(Context context, FetchCustomObjectListener listener) {
mListener = listener;
mContext = context;
}
#Override
protected CustomObject doInBackground(Context... params) {
return getObjectFromDb(mContext);
}
#Override
protected void onPostExecute(CustomObject d) {
super.onPostExecute(d);
mListener.onCustomObjectFetched(d);
}
}
}
Module A also contains an interface called FetchCustomObjectListener.class
public interface FetchCustomObjectListener {
public void onObjectFetched(CustomObject d);
}
Module B contains a class called Startup.class:
public class Startup {
private Startup(Context context) {
super(context);
Util.GetObjectTask getObjectTask = new Util.GetObjectTask(context, new FetchCustomObjectListener() {
#Override
public void onObjectFetched(CustomObject d) {
//handle custom object here
}
});
getObjectTask.execute();
}
At runtime the Startup class creates an instance of GetObjectTask and executes it. GetObjectTask grabs an object from the database and tries to return it to Startup class via the interface FetchObjectListener. At this point I am getting the following error:
java.lang.AbstractMethodError: abstract method "void a.b.c.FetchObjectListener.onObjectFetched(a.b.c.CustomObject)"
at a.b.c.Util$GetObjectTask.onPostExecute(SourceFile:65)
at a.b.c.Util$GetObjectTask.onPostExecute(SourceFile:48)
at android.os.AsyncTask.finish(AsyncTask.java:632)
at android.os.AsyncTask.access$600(AsyncTask.java:177)
at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:645)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5221)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
As I understand it, this error can occur when an interface is 'kept' by one module or class and not 'kept' by another when Dexguard is run. So one module has the actual name and one has the obfuscated name and because of this the two modules can't communicate using the interface and so the AbstractMethodError is thrown.
In the past I have used Dexguard to successfully compile and run this project, but have since modularized the project more and feel that this may be part of the problem. I'm trying to narrow down what could possibly be a problem and thought that perhaps two modules trying to use an interface might be causing the problem.
Any ideas on how to solve this would be appreciated.

It turns out to be an incorrect file filter on the first library when processing the second library. You may have seen the warnings about missing classes from the first library. The problem has been fixed in DexGuard 6.1.15.
Note that processing the final application (including its libraries) is more effective than processing the individual libraries, if you have the choice.

Related

How to correctly implement and test Custom Lint Rules in Android Studio?

I'm following this tutorial and this Custom Detector Example in order to implement Custom Lint Rules. Basically what I've done is:
Create a new Android Project in Android Studio;
Create a java module for project created in step 1;
On module's build.gradle, import Lint API dependencies;
Create an Issue & IssueRegistry & CustomDetector;
Reference the IssueRegistry on module's build.gradle;
Create Unit tests;
My problem is, during the execution of my JUnits, I always receive "No Warning". When I debug the test, I can see that my Custom Detector isn't called, what am I doing wrong?
Strings.java
public class Strings {
public static final String STR_ISSUE_001_ID = "VarsMustHaveMoreThanOneCharacter";
public static final String STR_ISSUE_001_DESCRIPTION = "Avoid naming variables with only one character";
public static final String STR_ISSUE_001_EXPLANATION = "Variables named with only one character do not pass any meaning to the reader. " +
"Variables name should clear indicate the meaning of the value it is holding";
}
Issues.java
public class Issues {
public static final
Issue ISSUE_001 = Issue.create(
STR_ISSUE_001_ID,
STR_ISSUE_001_DESCRIPTION,
STR_ISSUE_001_EXPLANATION,
SECURITY,
// Priority ranging from 0 to 10 in severeness
6,
WARNING,
new Implementation(VariableNameDetector.class, ALL_RESOURCES_SCOPE)
);
}
IssuesRegistry.java
public class IssueRegistry extends com.android.tools.lint.client.api.IssueRegistry {
#Override
public List<Issue> getIssues() {
List<Issue> issues = new ArrayList<>();
issues.add(ISSUE_001);
return issues;
}
}
VariableNameDetector.java
public class VariableNameDetector extends Detector implements Detector.JavaScanner {
public VariableNameDetector() {
}
#Override
public boolean appliesToResourceRefs() {
return false;
}
#Override
public boolean appliesTo(Context context, File file) {
return true;
}
#Override
#Nullable
public AstVisitor createJavaVisitor(JavaContext context) {
return new NamingConventionVisitor(context);
}
#Override
public List<String> getApplicableMethodNames() {
return null;
}
#Override
public List<Class<? extends Node>> getApplicableNodeTypes() {
List<Class<? extends Node>> types = new ArrayList<>(1);
types.add(lombok.ast.VariableDeclaration.class);
return types;
}
#Override
public void visitMethod(
JavaContext context,
AstVisitor visitor,
MethodInvocation methodInvocation
) {
}
#Override
public void visitResourceReference(
JavaContext context,
AstVisitor visitor,
Node node,
String type,
String name,
boolean isFramework
) {
}
private class NamingConventionVisitor extends ForwardingAstVisitor {
private final JavaContext context;
NamingConventionVisitor(JavaContext context) {
this.context = context;
}
#Override
public boolean visitVariableDeclaration(VariableDeclaration node) {
StrictListAccessor<VariableDefinitionEntry, VariableDeclaration> varDefinitions =
node.getVariableDefinitionEntries();
for (VariableDefinitionEntry varDefinition : varDefinitions) {
String name = varDefinition.astName().astValue();
if (name.length() == 1) {
context.report(
ISSUE_001,
context.getLocation(node),
STR_ISSUE_001_DESCRIPTION
);
return true;
}
}
return false;
}
}
}
build.gradle
apply plugin: 'java'
configurations {
lintChecks
}
ext {
VERSION_LINT_API = '24.3.1'
VERSION_LINT_API_TESTS = '24.3.1'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "com.android.tools.lint:lint-api:$VERSION_LINT_API"
implementation "com.android.tools.lint:lint-checks:$VERSION_LINT_API"
testImplementation "com.android.tools.lint:lint-tests:$VERSION_LINT_API_TESTS"
}
jar {
manifest {
attributes('Lint-Registry': 'br.com.edsilfer.lint_rules.resources.IssueRegistry')
}
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
TestVariableNameDetector.java
private static final String ARG_DEFAULT_LINT_SUCCESS_LOG = "No warnings.";
#Override
protected Detector getDetector() {
return new VariableNameDetector();
}
#Override
protected List<Issue> getIssues() {
return Collections.singletonList(Issues.ISSUE_001);
}
public void test_file_with_no_variables_with_length_equals_01() throws Exception {
assertEquals(
ARG_DEFAULT_LINT_SUCCESS_LOG,
lintProject(java("assets/Test.java", "public class Test {public String sampleVariable;}"))
);
}
public void test_file_with_variables_with_length_equals_01() throws Exception {
assertEquals(
ARG_DEFAULT_LINT_SUCCESS_LOG,
lintProject(java("assets/Test3.java", "public class Test {public String a;bnvhgvhj}"))
);
}
}
P.S.: on Java's module I do not have access to assetsor res folder, that is the reason why I've created a String.java and I'm using java(to, source) in my Unit test - I assumed that this java method does the same as the xml from the tutorial link I referenced at the top of this question.
It turned out that in my case the problem was with the JUnit itself. I think that the way I was attempting to simulate the file was wrong. The text below is part of the README.md of a sample project that I've created in order to document what I've learned from this API and answers the question in the title:
Create
Create a new Android Project;
Create a new Java Library Module - Custom Lint Rules are packaged into .jar libraries once they are ready, therefore the easiest way to implement them using them is inside a Java Module Library;
On module's build.gradle:
add target and source compatibility to Java 1.7;
add dependencies for lint-api, lint-checks and test dependencies;
add jar packing task containing two attributes: Manifest-Version and Lint-Registry, set the first to 1.0 and the second as the full path to a class that will later on contain the issue's catalog;
add a default tasl assemble;
[OPTIONAL]: add a task that will copy the generated .jar into ~/.android/lint;
Check REF001 and choose a Detector that best suits your needs, based on it create and implement a class to fulfill the Detector's role;
Still based on REF0001 chosen file, create and implement a Checker class, later referring to it inside Detector's createJavaVisitor() method;
for the sake of SRP, do not place Checker in the same file of Detector's class;
Copy the generated .jar file from build/lib to ~/.android/lint - if you added a task on build.gradle that does this you can skip this step;
Restart the computer - once created and moved into ~/.android/lint, the Custom Rules should be read by Lint next time the program starts. In order to have the alert boxes inside Android Studio, it is enough to invalidate caches and restart the IDE, however, to have your custom rules caught on Lint Report when ./gradlew check, it might be necessary to restart your computer;
Testing Detectors and Checkers
Testing Custom Rules is not an easy task to do - mainly due the lack of documentation for official APIs. This section will present two approaches for dealing with this. The main goal of this project is to create custom rules that will be run against real files, therefore, test files will be necessary for testing them. They can be places in src/test/resources folder from your Lint Java Library Module;
Approach 01: LintDetectorTest
Make sure you've added all test dependencies - checkout sample project's build.gradle;
Copy EnhancedLintDetectorTest.java and FileUtils.java into your project's test directory;
There is a known bug with Android Studio that prevents it from seeing files from src/test/resources folder, these files are a workaround for that;
EnhancedLintDetectorTest.java should return all issues that will be subject of tests. A nice way to do so is getting them from Issue Registry;
Create a test class that extends from EnhancedLintDetectorTest.java;
Implement getDetector() method returning an instance of the Detector to be tested;
Use lintFiles("test file path taking resources dir as root") to perform the check of the Custom Rules and use its result object to assert the tests;
Note that LintDetectorTest.java derives from TestCase.java, therefore, you're limited to JUnit 3.
Approach 02: Lint JUnit Rule
You might have noticed that Approach 01 might be a little overcomplicated, despite the fact that you're limited to JUnit 3 features. Because of that GitHub user a11n created a Lint JUnit Rule that allows the test of Custom Lint Rules in a easier way that counts with JUnit 4 and up features. Please, refer to his project README.md for details about how to create tests using this apprach.
Currently, Lint JUnit Rule do not correct the root dir for test files and you might no be able to see the tests passing from the IDE - however it works when test are run from command line. An issue and PR were created in order to fix this bug.
I'm not sure how to use the AST Api, however I'm personally using Psi and this is one of my lint checks that are working for me.
public final class RxJava2MethodCheckReturnValueDetector extends Detector implements Detector.JavaPsiScanner {
static final Issue ISSUE_METHOD_MISSING_CHECK_RETURN_VALUE =
Issue.create("MethodMissingCheckReturnValue", "Method is missing the #CheckReturnValue annotation",
"Methods returning RxJava Reactive Types should be annotated with the #CheckReturnValue annotation.",
MESSAGES, 8, WARNING,
new Implementation(RxJava2MethodCheckReturnValueDetector.class, EnumSet.of(JAVA_FILE, TEST_SOURCES)));
#Override public List<Class<? extends PsiElement>> getApplicablePsiTypes() {
return Collections.<Class<? extends PsiElement>>singletonList(PsiMethod.class);
}
#Override public JavaElementVisitor createPsiVisitor(#NonNull final JavaContext context) {
return new CheckReturnValueVisitor(context);
}
static class CheckReturnValueVisitor extends JavaElementVisitor {
private final JavaContext context;
CheckReturnValueVisitor(final JavaContext context) {
this.context = context;
}
#Override public void visitMethod(final PsiMethod method) {
final PsiType returnType = method.getReturnType();
if (returnType != null && Utils.isRxJava2TypeThatRequiresCheckReturnValueAnnotation(returnType)) {
final PsiAnnotation[] annotations = method.getModifierList().getAnnotations();
for (final PsiAnnotation annotation : annotations) {
if ("io.reactivex.annotations.CheckReturnValue".equals(annotation.getQualifiedName())) {
return;
}
}
final boolean isMethodMissingCheckReturnValueSuppressed = context.getDriver().isSuppressed(context, ISSUE_METHOD_MISSING_CHECK_RETURN_VALUE, method);
if (!isMethodMissingCheckReturnValueSuppressed) {
context.report(ISSUE_METHOD_MISSING_CHECK_RETURN_VALUE, context.getLocation(method.getNameIdentifier()), "Method should have #CheckReturnValue annotation");
}
}
}
}
}
Checkout the many more I wrote here.

Android 7.0 and 7.1 getApplication() ClassCastException

In Developer Console I see a lot of crashes with stacktrace like this
java.lang.RuntimeException:
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2984)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3045)
at android.app.ActivityThread.-wrap14(ActivityThread.java:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1642)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6776)
at java.lang.reflect.Method.invoke(Native Method:0)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)
Caused by: java.lang.ClassCastException:
at com.myapp.ui.BaseActivity.getApp(BaseActivity.java:193)
at com.myapp.ui.BaseActivity.onCreate(BaseActivity.java:275)
at com.myapp.ui.CastActivity.onCreate(CastActivity.java:39)
at com.myapp.ui.MainActivity.onCreate(MainActivity.java:268)
at android.app.Activity.performCreate(Activity.java:6955)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1126)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2927)
getApp method of BaseActivity is
public App getApp() {
return (App) getApplication();
}
App class is
public class App extends MultiDexApplication { ...
and in manifest application tag contains reference to this class
<application
android:name="com.myapp.App"
98% of crashes is for android 7.0, rest is 7.1. No other android versions are affected.
EDIT: I use proguard so it can be somehow related but keeping class
-keep class com.myapp.** { *;}
-keep interface com.myapp.** { *;}
Note: It may not be related but in same android versions it looks like App's onCreate method is sometimes not called. I observed it because some objects which are created in onCreate were null when they were accessed from Service (started by AlarmManager) or BroadcastReceiver
Does anyone has idea what can cause it, how to fix it or work around it? Thanks
EDIT 2:
I ended up with something like this:
public App getApp() {
Application application = getApplication();
App app = null;
try {
app = (App) application;
} catch (Exception e) {
if (application != null) {
Log.e(TAG, "getApp Exception: application class: " + application.getClass().getName());
} else {
Log.e(TAG, "getApp Exception: application object is null");
}
}
return app;
}
It at least doesn't crash and I can check getApp() == null
Casting fails because getApplication() returns an Application and NOT the desired sub-class.
I've had some success where I caught the error and asked the user to reboot their device or reinstall the app.
Unfortunately, there's no real fix to this rare crash. Google won't fix the lifecycle-related issue, but said it reduced in Android 7.1+. Source: https://issuetracker.google.com/issues/37137009
I think you should cast getApplicationContext() into App instead.
While I cannot say if this solution works.
I think that static Application instance should solve the problem.
class MyApp extends Application {
private static final sInstance;
public void onCreate() {
sInstance = this;
}
public static MyApp getInstance() {
return sInstance;
}
}
Instead of calling getActivity() if you call MyApp.getInstance() you should not need to cast. So there should not be any ClassCastException
anymore.
You should override attachBaseContext in your application class like this:
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
Check this link for more information: https://developer.android.com/reference/android/support/multidex/MultiDexApplication.html
This might help
public class App extends MultiDexApplication {
public static App app = null;
public static App getInstance() {
return app;
}
#Override
public void onCreate() {
super.onCreate();
app = this;
}
}
you doesn't need to cast getApplication(), reason is you are already in Application class so simply just use this keyword to get application instance. Hope you find useful

Dagger 2 Build IllegalArgumentException compileDebugJavaWithJavac

I have been testing out Dagger 2, and everything had been working, until I did a bit of refactoring. Now gradle is throwing an IllegalArgumentException, and I cannot figure out what I changed that is now causing the error. I haven't made any changes to the gradle file, and this seems to be the brunt of the stack trace:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':mobile:compileDebugJavaWithJavac'.
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:69)
...
Caused by: java.lang.IllegalArgumentException
at com.google.common.base.Preconditions.checkArgument(Preconditions.java:108)
at dagger.internal.codegen.writer.ClassName.peerNamed(ClassName.java:130)
at dagger.internal.codegen.SourceFiles.membersInjectorNameForMembersInjectionBinding(SourceFiles.java:266)
at dagger.internal.codegen.InjectBindingRegistry.registerBinding(InjectBindingRegistry.java:194)
at dagger.internal.codegen.InjectBindingRegistry.registerBinding(InjectBindingRegistry.java:171)
at dagger.internal.codegen.InjectProcessingStep.process(InjectProcessingStep.java:129)
at dagger.shaded.auto.common.BasicAnnotationProcessor.process(BasicAnnotationProcessor.java:228)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.java:794)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:705)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.access$1800(JavacProcessingEnvironment.java:91)
at com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1035)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1176)
at com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1170)
at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:856)
at com.sun.tools.javac.main.Main.compile(Main.java:523)
... 89 more
No files are being generated by Dagger as well, and they were previously. I have been trying every method to fix this that I can find, mostly involving fixing the gradle files or clearing out the build folder, but so far nothing has worked.
Quick update (since I noticed a few up-votes); I never did find out what I did wrong, I ended up reverting to an old build. After the revert, I did the refactoring again and it worked fine. I must've done something different when I initially refactored the code, but I have no idea what it was.
If anyone has an idea of what could have caused this, I'm sure it will help out anyone else who has, or will in the future, run into this issue.
I ran into this issue while bringing Firebase into the project. It was the first background service being added to the project so I decided to do some sleuthing with a service that did nothing.
This built:
public class HopefullyBuildsService extends IntentService {
public HopefullyBuildsService(String name) {
super(name);
}
#Override
protected void onHandleIntent(Intent intent) {
}
}
..............
#ApplicationScoped
#Component(modules = {ApplicationModule.class, RestModule.class})
public interface ApplicationComponent {
...
void inject(HopefullyBuildsService service);
...
}
But this caused the build to fail:
public class HopefullyBuildsService extends FirebaseMessagingService {
}
..............
#ApplicationScoped
#Component(modules = {ApplicationModule.class, RestModule.class})
public interface ApplicationComponent {
...
void inject(HopefullyBuildsService service);
...
}
For whatever reason trying to inject directly into a Firebase derived service causes the build to fail in the way you described. However indirectly injecting into another class and then instantiating it the old-fashioned way inside the service allowed it to build again.
public class FirebaseDaggerInjectHelper {
#Inject
PersistManager persistManager;
#Inject
RestClient restClient;
#Inject
OtherClasses stuffEtc;
public FirebaseDaggerInjectHelper(MyApplication application){
application.getApplicationComponent().inject(this);
}
//getters and setters n stuff
}
........
#ApplicationScoped
#Component(modules = {ApplicationModule.class, RestModule.class})
public interface ApplicationComponent {
...
void inject(FirebaseDaggerInjectHelper helper);
...
}
........
public class HopefullyBuildsService extends FirebaseMessagingService {
private FirebaseDaggerInjectHelper injectHelper;
#Override
public void onCreate() {
super.onCreate();
injectHelper = new FirebaseDaggerInjectHelper((MyApplication) getApplicationContext());
}
And then it built fine. Admittedly, having this middleman class is annoying and the firebase derived service has to interact with the injected components in an indirect fashion. But its not clear to me why I cannot inject into a Firebase derived service, Or what is special about Firebase that made Dagger2 unhappy.
This is not yet solved in dependency dagger2.0 still throws IllegalArgumentException, I agree with #KATHYxx's approach to solve it in dagger2.0
But square has solved the inject in dagger2.7 version.
So, updating the dependency fixed the issue
implementation "com.google.dagger:dagger:2.7"
apt "com.google.dagger:dagger-compiler:2.7"

How can I inject parameter through constructor in Roboguice? [android]

This question is probably exact duplicate of this one
Pass parameter to constructor with Guice
Difference is that I use roboguice for android, not just Guice, so answers there does not work for me.
Question is - how can I pass initialize parameters into created object? I.e. I have injected interface which should be initialize with some parameter which roboguice does not know.
What I see in link I provide, I should create factory interface and register it like this
void configure(Binder binder) {
binder.install(new FactoryModuleBuilder()
.implement(FooInterface.class, Foo.class)
.build(FooFactory.class));
}
But I can't find FactoryModuleBuilder class. I use Intellij IDEA, it can show me every class which I can access at current place and I can be 100% sure that there is no classes which starts with 'Factory' word.
How can I create my factory using roboguice?
UPDATED
I forgot to download guice-assistedinject. But still I can't figure out where should I register this factory.
UPDATE 2
Why I need that? Because there should be situation where some abstraction has dependency which could not be resolved by Roboguice. This dependency could be any type, even simple string or number.
In my case I have NumberPicker control on UI and I want to move all UI specific tasks in MyNumberPickerWrapper class. And when I create this wrapper I inject its dependency (this control) through constructor.
It's not the point if I am right with such approach, but that there could be a plenty of another more applicable example where constructor injection needed and this injected classes could not be created by Roboguice
I followed the steps of the answer given in Pass parameter to constructor with Guice and did slight modifications to run it under roboguice. Works completely fine for me.
add guice-assistinject library to gradle script
dependencies { compile 'com.google.inject.extensions:guice-assistedinject:4.+' }
Create Factory interface that with create method that accepts parameters the object constructor requires and returns object's interface
public interface ICustomObjectFactory {
ICustomObject create(String queueName);
}
Add #Inject annotation to constructor of the object and #Assisted annotation to each parameter coming from factory.
public class CustomObject implements ICustomObject {
protected String name;
#Inject
public CustomObject(#Assisted String name){
this.name = name;
}
}
Add binding into the Module that you are using
public class SomeModule extends AbstractModule {
#Override
protected void configure() {
install(new FactoryModuleBuilder()
.implement(ICustomObject.class, CustomObject.class)
.build(ICustomObjectFactory.class));
}
}
Inject factory and create instances of your object
public class SomeClass {
#Inject ICustomObjectFactory factory;
public SomeClass () {
ICustomObject first = this.factory.create("first");
ICustomObject second = this.factory.create("second");
}
}
I faced this same problem and I succeed thanks to Pavel's answer. I only have had to struggle with some errors, and I don't know if it's due to the versions of the libraries used, but for me didn't work without modifying the annotation of the constructor, replacing #Inject by #AssistedInject. With that, the code of the class that implements the interface looks like this.
public class CustomObject implements ICustomObject {
protected String name;
#AssistedInject
public CustomObject(#Assisted String name){
this.name = name;
}
}

What version of RoboGuice works with Android SDK 8?

That's probably part one of my question.
Basically I'm struggling with the actual injection for version 1.1.2. I've read the couple of pages on the site, and I feel I'm missing something.
Basically I've done the RoboApplication extension. I've overridden the addApplicationModules method. I've even made a module.
My module looks like this:
public class DataRepository extends AbstractAndroidModule
{
#Override
protected void configure() {
/*
* This tells Guice that whenever it sees a dependency on a TransactionLog,
* it should satisfy the dependency using a DatabaseTransactionLog.
*/
bind(IDataBaseAdapter.class).to(DataBaseAdapter.class);
}
}
In my adapter I have this:
public class DataBaseAdapter implements IDataBaseAdapter
{
private DataBaseHelper _dbHelper;
private SQLiteDatabase _db;
#Inject
protected static Provider<Context> contextProvider;
public DataBaseAdapter()
{
_dbHelper = new DataBaseHelper(contextProvider.get());
}
}
If I don't do there, where is the opportune place for the chunk of code to reside... where I associate injectors?
Finally... my Application has an injection of it like so:
public class MyApplication extends RoboApplication
{
public MyApplication()
{
super();
}
public MyApplication(Context context)
{
super();
attachBaseContext(context);
}
#Override
protected void addApplicationModules(List<Module> modules)
{
modules.add(new DataRepository());
}
#Inject
private IDataBaseAdapter adapter;
public IDataBaseAdapter getAdapter()
{
return adapter;
}
public void setAdapter(IDataBaseAdapter value)
{
adapter = value;
}
...
}
I'm trying to use the Inject attribute as shown. For example:
#Inject
private IDataProvider provider;
A couple of reasons why I'm lost is that I come from a .NET and Flash/ActionScript background plus I've only used StructureMap instead of Ninject (in the .NET world), which I've heard Guice is designed with some of the ideas of Ninject in mind. Could someone help me figure out this small piece?
I'd really like to focus on using 1.1.2 instead of jumping to 2.x of RoboGuice... especially since it is still in beta, so I hope you all don't mind.
Thanks again,
Kelly
Android is quite different from standalone / hosted java application. You do not have main() , but you have certain activity units, which are managed by android framework (activities, services , broadcast receivers)
DI is a technique which allows you to eliminate booler plate code by wiring together
parts in good object oriented way.
As your unit of work is mostly activity, you shall do wiring / creation of your collaborating objects in onCreate() method , and there are dedicated onResume() and onPause() methods (see actviity lifecycle)
Rule of thumb is, does this thing needs to be restarted every time activity loses it focus? If yes, initialize / destroy it in inResume() / onPause(), otherwise - in onCreate()
And if you like to share objects withing entire application ( running in same JVM ) , it is OK to use singleton pattern in android. So you may just have singleton injector factory , and cosult it from everywhere:
InjectorFactory.getInstance(<context if necessary?>).getInstance(whatever you need);
OK, I've figured out what was needed, but I'm not quite sure why after seeing all the information floating out there.
I basically made this change, and now my test passes.
public class DataBaseAdapter implements IDataBaseAdapter
{
private DataBaseHelper _dbHelper;
private SQLiteDatabase _db;
#Inject
public DataBaseAdapter(Provider<Context> contextProvider)
{
_dbHelper = new DataBaseHelper(contextProvider.get());
}
}
While I like using constructors as the tool for injecting, I wonder why it had to work this way, considering that examples I have seen are some kind of reflection class injection.
Anyway, that's this part. Hopefully someone else will find this useful.
Cheers,
Kelly

Categories

Resources