I'm trying to create a Lint rule in my Android code that checks the number if injections in a constructor, so if I exceed a certain number for a view model, for example, I will raise a lint warning.
I know that I have to implement a UastScanner in my Lint Detector, but I am getting lost because I cannot find good documentation. Has someone else ever done something like this? Or where can I find good deocumentation about it?
Thanks!
* NOTE - Read entire answer for edited solution. *
I was able to write the UAST conversion like so:
public class NumberOfDependenciesInjectedDetector extends Detector implements Detector.UastScanner {
private static final int NUMBER_OF_INJECTIONS_ALLOWED = 5;
private static final Class<? extends Detector> DETECTOR = NumberOfDependenciesInjectedDetector.class;
private static final EnumSet<Scope> SCOPE = Scope.JAVA_FILE_SCOPE;
private static final Implementation IMPLEMENTATION = new Implementation(DETECTOR, SCOPE);
public static final Issue ISSUE = Issue.create(
"NumberOfDependenciesInjected",
"Limit number of injections in a class via constructor",
"A longer description here",
Category.CORRECTNESS,
10,
Severity.ERROR,
IMPLEMENTATION
);
#Override
public List<Class<? extends UElement>> getApplicableUastTypes() {
return Collections.<Class<? extends UElement>>singletonList(UClass.class);
}
#Override
public UElementHandler createUastHandler(JavaContext context) {
return new ConstructorVisitor(context);
}
private static class ConstructorVisitor extends UElementHandler {
private JavaContext javaContext;
private ConstructorVisitor(JavaContext javaContext) {
this.javaContext = javaContext;
}
#Override
public void visitClass(UClass clazz){
UMethod[] methods = clazz.getMethods();
for(UMethod method : methods){
if(!method.isConstructor()) continue;
if (method.getParameterList().getParametersCount() > NUMBER_OF_INJECTIONS_ALLOWED) {
javaContext.report(ISSUE, method, javaContext.getLocation(method), "Injections exceed allowed value of " + NUMBER_OF_INJECTIONS_ALLOWED);
}
}
}
}
}
However, it seems that this still doesn't pick up Kotlin source files... very confusing.
EDIT: 12/19/17 - FIXED
The problem was two-fold:
1) There was indeed a hidden usage of a Psi method that was blocking the check from working. The visitClass method should not use getParameterList() but instead, getUastParameters(). Replace visitclass above with:
#Override
public void visitClass(UClass clazz){
UMethod[] methods = clazz.getMethods();
for(UMethod method : methods){
if(!method.isConstructor()) continue;
if (method.getUastParameters().size() > NUMBER_OF_INJECTIONS_ALLOWED) {
javaContext.report(ISSUE, clazz, javaContext.getLocation(method), "Injections exceed allowed value of " + NUMBER_OF_INJECTIONS_ALLOWED);
}
}
}
2) After speaking with Tor Norbye directly on the lint-dev group, he pointed out that Android Studio 3.0 in fact lint does not work externally with kotlin and therefore was not expected to work as described here. Upgrading to Android Studio 3.1 Canary and running the linter produced the expected report.
So I was able to find a solution using JavaScanner, but I haven't found anything yet using UastScanner (which it is what I want to use, since we have Kotlin classes too):
public class NumberOfDependenciesInjectedDetector extends Detector implements Detector.JavaScanner {
private static final int NUMBER_OF_INJECTIONS_ALLOWED = 5;
private static final Class<? extends Detector> DETECTOR = NumberOfDependenciesInjectedDetector.class;
private static final EnumSet<Scope> SCOPE = Scope.JAVA_FILE_SCOPE;
private static final Implementation IMPLEMENTATION = new Implementation(DETECTOR, SCOPE);
public static final Issue ISSUE = Issue.create(
"NumberOfDependenciesInjected",
"Limit number of injections in a class via constructor",
"A longer description here",
Category.CORRECTNESS,
10,
Severity.ERROR,
IMPLEMENTATION
);
#Override
public boolean appliesTo(Context context, File file) {
return true;
}
#Override
public Speed getSpeed(Issue issue) {
return Speed.FAST;
}
#Override
public List<Class<? extends Node>> getApplicableNodeTypes() {
return Collections.<Class<? extends Node>>singletonList(ConstructorDeclaration.class);
}
#Override
public AstVisitor createJavaVisitor(JavaContext context) {
return new ConstructorVisitor(context);
}
private static class ConstructorVisitor extends ForwardingAstVisitor {
private JavaContext javaContext;
private ConstructorVisitor(JavaContext javaContext) {
this.javaContext = javaContext;
}
#Override
public boolean visitConstructorDeclaration(ConstructorDeclaration node) {
if (node.astParameters().size() > NUMBER_OF_INJECTIONS_ALLOWED) {
javaContext.report(ISSUE, node, javaContext.getLocation(node), "My message goes here");
return true;
}
return false;
}
}
}
I read this question and thought that would easily be solved (not that it isn't solvable without) if one could write:
#Override
public String toString() {
return super.super.toString();
}
I'm not sure if it is useful in many cases, but I wonder why it isn't and if something like this exists in other languages.
What do you guys think?
EDIT:
To clarify: yes I know, that's impossible in Java and I don't really miss it. This is nothing I expected to work and was surprised getting a compiler error. I just had the idea and like to discuss it.
It violates encapsulation. You shouldn't be able to bypass the parent class's behaviour. It makes sense to sometimes be able to bypass your own class's behaviour (particularly from within the same method) but not your parent's. For example, suppose we have a base "collection of items", a subclass representing "a collection of red items" and a subclass of that representing "a collection of big red items". It makes sense to have:
public class Items
{
public void add(Item item) { ... }
}
public class RedItems extends Items
{
#Override
public void add(Item item)
{
if (!item.isRed())
{
throw new NotRedItemException();
}
super.add(item);
}
}
public class BigRedItems extends RedItems
{
#Override
public void add(Item item)
{
if (!item.isBig())
{
throw new NotBigItemException();
}
super.add(item);
}
}
That's fine - RedItems can always be confident that the items it contains are all red. Now suppose we were able to call super.super.add():
public class NaughtyItems extends RedItems
{
#Override
public void add(Item item)
{
// I don't care if it's red or not. Take that, RedItems!
super.super.add(item);
}
}
Now we could add whatever we like, and the invariant in RedItems is broken.
Does that make sense?
I think Jon Skeet has the correct answer. I'd just like to add that you can access shadowed variables from superclasses of superclasses by casting this:
interface I { int x = 0; }
class T1 implements I { int x = 1; }
class T2 extends T1 { int x = 2; }
class T3 extends T2 {
int x = 3;
void test() {
System.out.println("x=\t\t" + x);
System.out.println("super.x=\t\t" + super.x);
System.out.println("((T2)this).x=\t" + ((T2)this).x);
System.out.println("((T1)this).x=\t" + ((T1)this).x);
System.out.println("((I)this).x=\t" + ((I)this).x);
}
}
class Test {
public static void main(String[] args) {
new T3().test();
}
}
which produces the output:
x= 3
super.x= 2
((T2)this).x= 2
((T1)this).x= 1
((I)this).x= 0
(example from the JLS)
However, this doesn't work for method calls because method calls are determined based on the runtime type of the object.
I think the following code allow to use super.super...super.method() in most case.
(even if it's uggly to do that)
In short
create temporary instance of ancestor type
copy values of fields from original object to temporary one
invoke target method on temporary object
copy modified values back to original object
Usage :
public class A {
public void doThat() { ... }
}
public class B extends A {
public void doThat() { /* don't call super.doThat() */ }
}
public class C extends B {
public void doThat() {
Magic.exec(A.class, this, "doThat");
}
}
public class Magic {
public static <Type, ChieldType extends Type> void exec(Class<Type> oneSuperType, ChieldType instance,
String methodOfParentToExec) {
try {
Type type = oneSuperType.newInstance();
shareVars(oneSuperType, instance, type);
oneSuperType.getMethod(methodOfParentToExec).invoke(type);
shareVars(oneSuperType, type, instance);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static <Type, SourceType extends Type, TargetType extends Type> void shareVars(Class<Type> clazz,
SourceType source, TargetType target) throws IllegalArgumentException, IllegalAccessException {
Class<?> loop = clazz;
do {
for (Field f : loop.getDeclaredFields()) {
if (!f.isAccessible()) {
f.setAccessible(true);
}
f.set(target, f.get(source));
}
loop = loop.getSuperclass();
} while (loop != Object.class);
}
}
I don't have enough reputation to comment so I will add this to the other answers.
Jon Skeet answers excellently, with a beautiful example. Matt B has a point: not all superclasses have supers. Your code would break if you called a super of a super that had no super.
Object oriented programming (which Java is) is all about objects, not functions. If you want task oriented programming, choose C++ or something else. If your object doesn't fit in it's super class, then you need to add it to the "grandparent class", create a new class, or find another super it does fit into.
Personally, I have found this limitation to be one of Java's greatest strengths. Code is somewhat rigid compared to other languages I've used, but I always know what to expect. This helps with the "simple and familiar" goal of Java. In my mind, calling super.super is not simple or familiar. Perhaps the developers felt the same?
There's some good reasons to do this. You might have a subclass which has a method which is implemented incorrectly, but the parent method is implemented correctly. Because it belongs to a third party library, you might be unable/unwilling to change the source. In this case, you want to create a subclass but override one method to call the super.super method.
As shown by some other posters, it is possible to do this through reflection, but it should be possible to do something like
(SuperSuperClass this).theMethod();
I'm dealing with this problem right now - the quick fix is to copy and paste the superclass method into the subsubclass method :)
In addition to the very good points that others have made, I think there's another reason: what if the superclass does not have a superclass?
Since every class naturally extends (at least) Object, super.whatever() will always refer to a method in the superclass. But what if your class only extends Object - what would super.super refer to then? How should that behavior be handled - a compiler error, a NullPointer, etc?
I think the primary reason why this is not allowed is that it violates encapsulation, but this might be a small reason too.
I think if you overwrite a method and want to all the super-class version of it (like, say for equals), then you virtually always want to call the direct superclass version first, which one will call its superclass version in turn if it wants.
I think it only makes rarely sense (if at all. i can't think of a case where it does) to call some arbitrary superclass' version of a method. I don't know if that is possible at all in Java. It can be done in C++:
this->ReallyTheBase::foo();
At a guess, because it's not used that often. The only reason I could see using it is if your direct parent has overridden some functionality and you're trying to restore it back to the original.
Which seems to me to be against OO principles, since the class's direct parent should be more closely related to your class than the grandparent is.
Calling of super.super.method() make sense when you can't change code of base class. This often happens when you are extending an existing library.
Ask yourself first, why are you extending that class? If answer is "because I can't change it" then you can create exact package and class in your application, and rewrite naughty method or create delegate:
package com.company.application;
public class OneYouWantExtend extends OneThatContainsDesiredMethod {
// one way is to rewrite method() to call super.method() only or
// to doStuff() and then call super.method()
public void method() {
if (isDoStuff()) {
// do stuff
}
super.method();
}
protected abstract boolean isDoStuff();
// second way is to define methodDelegate() that will call hidden super.method()
public void methodDelegate() {
super.method();
}
...
}
public class OneThatContainsDesiredMethod {
public void method() {...}
...
}
For instance, you can create org.springframework.test.context.junit4.SpringJUnit4ClassRunner class in your application so this class should be loaded before the real one from jar. Then rewrite methods or constructors.
Attention: This is absolute hack, and it is highly NOT recommended to use but it's WORKING! Using of this approach is dangerous because of possible issues with class loaders. Also this may cause issues each time you will update library that contains overwritten class.
#Jon Skeet Nice explanation.
IMO if some one wants to call super.super method then one must be want to ignore the behavior of immediate parent, but want to access the grand parent behavior.
This can be achieved through instance Of. As below code
public class A {
protected void printClass() {
System.out.println("In A Class");
}
}
public class B extends A {
#Override
protected void printClass() {
if (!(this instanceof C)) {
System.out.println("In B Class");
}
super.printClass();
}
}
public class C extends B {
#Override
protected void printClass() {
System.out.println("In C Class");
super.printClass();
}
}
Here is driver class,
public class Driver {
public static void main(String[] args) {
C c = new C();
c.printClass();
}
}
Output of this will be
In C Class
In A Class
Class B printClass behavior will be ignored in this case.
I am not sure about is this a ideal or good practice to achieve super.super, but still it is working.
Look at this Github project, especially the objectHandle variable. This project shows how to actually and accurately call the grandparent method on a grandchild.
Just in case the link gets broken, here is the code:
import lombok.val;
import org.junit.Assert;
import org.junit.Test;
import java.lang.invoke.*;
/*
Your scientists were so preoccupied with whether or not they could, they didn’t stop to think if they should.
Please don't actually do this... :P
*/
public class ImplLookupTest {
private MethodHandles.Lookup getImplLookup() throws NoSuchFieldException, IllegalAccessException {
val field = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
field.setAccessible(true);
return (MethodHandles.Lookup) field.get(null);
}
#Test
public void test() throws Throwable {
val lookup = getImplLookup();
val baseHandle = lookup.findSpecial(Base.class, "toString",
MethodType.methodType(String.class),
Sub.class);
val objectHandle = lookup.findSpecial(Object.class, "toString",
MethodType.methodType(String.class),
// Must use Base.class here for this reference to call Object's toString
Base.class);
val sub = new Sub();
Assert.assertEquals("Sub", sub.toString());
Assert.assertEquals("Base", baseHandle.invoke(sub));
Assert.assertEquals(toString(sub), objectHandle.invoke(sub));
}
private static String toString(Object o) {
return o.getClass().getName() + "#" + Integer.toHexString(o.hashCode());
}
public class Sub extends Base {
#Override
public String toString() {
return "Sub";
}
}
public class Base {
#Override
public String toString() {
return "Base";
}
}
}
Happy Coding!!!!
I would put the super.super method body in another method, if possible
class SuperSuperClass {
public String toString() {
return DescribeMe();
}
protected String DescribeMe() {
return "I am super super";
}
}
class SuperClass extends SuperSuperClass {
public String toString() {
return "I am super";
}
}
class ChildClass extends SuperClass {
public String toString() {
return DescribeMe();
}
}
Or if you cannot change the super-super class, you can try this:
class SuperSuperClass {
public String toString() {
return "I am super super";
}
}
class SuperClass extends SuperSuperClass {
public String toString() {
return DescribeMe(super.toString());
}
protected String DescribeMe(string fromSuper) {
return "I am super";
}
}
class ChildClass extends SuperClass {
protected String DescribeMe(string fromSuper) {
return fromSuper;
}
}
In both cases, the
new ChildClass().toString();
results to "I am super super"
It would seem to be possible to at least get the class of the superclass's superclass, though not necessarily the instance of it, using reflection; if this might be useful, please consider the Javadoc at http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Class.html#getSuperclass()
public class A {
#Override
public String toString() {
return "A";
}
}
public class B extends A {
#Override
public String toString() {
return "B";
}
}
public class C extends B {
#Override
public String toString() {
return "C";
}
}
public class D extends C {
#Override
public String toString() {
String result = "";
try {
result = this.getClass().getSuperclass().getSuperclass().getSuperclass().newInstance().toString();
} catch (InstantiationException ex) {
Logger.getLogger(D.class.getName()).log(Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
Logger.getLogger(D.class.getName()).log(Level.SEVERE, null, ex);
}
return result;
}
}
public class Main {
public static void main(String... args) {
D d = new D();
System.out.println(d);
}
}
run:
A
BUILD SUCCESSFUL (total time: 0 seconds)
I have had situations like these when the architecture is to build common functionality in a common CustomBaseClass which implements on behalf of several derived classes.
However, we need to circumvent common logic for specific method for a specific derived class. In such cases, we must use a super.super.methodX implementation.
We achieve this by introducing a boolean member in the CustomBaseClass, which can be used to selectively defer custom implementation and yield to default framework implementation where desirable.
...
FrameworkBaseClass (....) extends...
{
methodA(...){...}
methodB(...){...}
...
methodX(...)
...
methodN(...){...}
}
/* CustomBaseClass overrides default framework functionality for benefit of several derived classes.*/
CustomBaseClass(...) extends FrameworkBaseClass
{
private boolean skipMethodX=false;
/* implement accessors isSkipMethodX() and setSkipMethodX(boolean)*/
methodA(...){...}
methodB(...){...}
...
methodN(...){...}
methodX(...){
if (isSkipMethodX()) {
setSKipMethodX(false);
super.methodX(...);
return;
}
... //common method logic
}
}
DerivedClass1(...) extends CustomBaseClass
DerivedClass2(...) extends CustomBaseClass
...
DerivedClassN(...) extends CustomBaseClass...
DerivedClassX(...) extends CustomBaseClass...
{
methodX(...){
super.setSKipMethodX(true);
super.methodX(...);
}
}
However, with good architecture principles followed in framework as well as app, we could avoid such situations easily, by using hasA approach, instead of isA approach. But at all times it is not very practical to expect well designed architecture in place, and hence the need to get away from solid design principles and introduce hacks like this.
Just my 2 cents...
IMO, it's a clean way to achieve super.super.sayYourName() behavior in Java.
public class GrandMa {
public void sayYourName(){
System.out.println("Grandma Fedora");
}
}
public class Mama extends GrandMa {
public void sayYourName(boolean lie){
if(lie){
super.sayYourName();
}else {
System.out.println("Mama Stephanida");
}
}
}
public class Daughter extends Mama {
public void sayYourName(boolean lie){
if(lie){
super.sayYourName(lie);
}else {
System.out.println("Little girl Masha");
}
}
}
public class TestDaughter {
public static void main(String[] args){
Daughter d = new Daughter();
System.out.print("Request to lie: d.sayYourName(true) returns ");
d.sayYourName(true);
System.out.print("Request not to lie: d.sayYourName(false) returns ");
d.sayYourName(false);
}
}
Output:
Request to lie: d.sayYourName(true) returns Grandma Fedora
Request not to lie: d.sayYourName(false) returns Little girl Masha
I think this is a problem that breaks the inheritance agreement.
By extending a class you obey / agree its behavior, features
Whilst when calling super.super.method(), you want to break your own obedience agreement.
You just cannot cherry pick from the super class.
However, there may happen situations when you feel the need to call super.super.method() - usually a bad design sign, in your code or in the code you inherit !
If the super and super super classes cannot be refactored (some legacy code), then opt for composition over inheritance.
Encapsulation breaking is when you #Override some methods by breaking the encapsulated code.
The methods designed not to be overridden are marked
final.
In C# you can call a method of any ancestor like this:
public class A
internal virtual void foo()
...
public class B : A
public new void foo()
...
public class C : B
public new void foo() {
(this as A).foo();
}
Also you can do this in Delphi:
type
A=class
procedure foo;
...
B=class(A)
procedure foo; override;
...
C=class(B)
procedure foo; override;
...
A(objC).foo();
But in Java you can do such focus only by some gear. One possible way is:
class A {
int y=10;
void foo(Class X) throws Exception {
if(X!=A.class)
throw new Exception("Incorrect parameter of "+this.getClass().getName()+".foo("+X.getName()+")");
y++;
System.out.printf("A.foo(%s): y=%d\n",X.getName(),y);
}
void foo() throws Exception {
System.out.printf("A.foo()\n");
this.foo(this.getClass());
}
}
class B extends A {
int y=20;
#Override
void foo(Class X) throws Exception {
if(X==B.class) {
y++;
System.out.printf("B.foo(%s): y=%d\n",X.getName(),y);
} else {
System.out.printf("B.foo(%s) calls B.super.foo(%s)\n",X.getName(),X.getName());
super.foo(X);
}
}
}
class C extends B {
int y=30;
#Override
void foo(Class X) throws Exception {
if(X==C.class) {
y++;
System.out.printf("C.foo(%s): y=%d\n",X.getName(),y);
} else {
System.out.printf("C.foo(%s) calls C.super.foo(%s)\n",X.getName(),X.getName());
super.foo(X);
}
}
void DoIt() {
try {
System.out.printf("DoIt: foo():\n");
foo();
Show();
System.out.printf("DoIt: foo(B):\n");
foo(B.class);
Show();
System.out.printf("DoIt: foo(A):\n");
foo(A.class);
Show();
} catch(Exception e) {
//...
}
}
void Show() {
System.out.printf("Show: A.y=%d, B.y=%d, C.y=%d\n\n", ((A)this).y, ((B)this).y, ((C)this).y);
}
}
objC.DoIt() result output:
DoIt: foo():
A.foo()
C.foo(C): y=31
Show: A.y=10, B.y=20, C.y=31
DoIt: foo(B):
C.foo(B) calls C.super.foo(B)
B.foo(B): y=21
Show: A.y=10, B.y=21, C.y=31
DoIt: foo(A):
C.foo(A) calls C.super.foo(A)
B.foo(A) calls B.super.foo(A)
A.foo(A): y=11
Show: A.y=11, B.y=21, C.y=31
It is simply easy to do. For instance:
C subclass of B and B subclass of A. Both of three have method methodName() for example.
public abstract class A {
public void methodName() {
System.out.println("Class A");
}
}
public class B extends A {
public void methodName() {
super.methodName();
System.out.println("Class B");
}
// Will call the super methodName
public void hackSuper() {
super.methodName();
}
}
public class C extends B {
public static void main(String[] args) {
A a = new C();
a.methodName();
}
#Override
public void methodName() {
/*super.methodName();*/
hackSuper();
System.out.println("Class C");
}
}
Run class C Output will be:
Class A
Class C
Instead of output:
Class A
Class B
Class C
If you think you are going to be needing the superclass, you could reference it in a variable for that class. For example:
public class Foo
{
public int getNumber()
{
return 0;
}
}
public class SuperFoo extends Foo
{
public static Foo superClass = new Foo();
public int getNumber()
{
return 1;
}
}
public class UltraFoo extends Foo
{
public static void main(String[] args)
{
System.out.println(new UltraFoo.getNumber());
System.out.println(new SuperFoo().getNumber());
System.out.println(new SuperFoo().superClass.getNumber());
}
public int getNumber()
{
return 2;
}
}
Should print out:
2
1
0
public class SubSubClass extends SubClass {
#Override
public void print() {
super.superPrint();
}
public static void main(String[] args) {
new SubSubClass().print();
}
}
class SuperClass {
public void print() {
System.out.println("Printed in the GrandDad");
}
}
class SubClass extends SuperClass {
public void superPrint() {
super.print();
}
}
Output: Printed in the GrandDad
The keyword super is just a way to invoke the method in the superclass.
In the Java tutorial:https://docs.oracle.com/javase/tutorial/java/IandI/super.html
If your method overrides one of its superclass's methods, you can invoke the overridden method through the use of the keyword super.
Don't believe that it's a reference of the super object!!! No, it's just a keyword to invoke methods in the superclass.
Here is an example:
class Animal {
public void doSth() {
System.out.println(this); // It's a Cat! Not an animal!
System.out.println("Animal do sth.");
}
}
class Cat extends Animal {
public void doSth() {
System.out.println(this);
System.out.println("Cat do sth.");
super.doSth();
}
}
When you call cat.doSth(), the method doSth() in class Animal will print this and it is a cat.
I'm new to Android and MVP in-general, and I've been doing iOS programming for the last 1.5 years, so I find delegate patterns easy to digest. I've implemented MVP in such a way that the view conforms to a presenter's protocol, which lets the presenter disregard the view's specific type, but lets it know that certain methods are a given and thus okay to call on the "view." I've been reading various MVP guides, and all of the Mosby tutorials, and I'm not sure I agree with some of it. Is the pattern I've implemented kosher? I'd like some feedback so I don't keep heading in a bad direction, if that is indeed what I'm doing...
For example,
Base Presenter:
public abstract class Presenter<V, S> implements BasePresenterInterface<V, S> {
public interface PresenterProtocol extends BasePresenterProtocol {
}
private WeakReference<V> mAttachedView = null;
private S mService = null;
/**
* Interface Overrides
*/
#Override
public void attachView(V view) {
boolean viewDoesNotConform = !viewDoesConform(view);
if (viewDoesNotConform) {
Log.d("DEBUG", "Cannot attach View that does not conform to PresenterProtocol");
return;
}
mAttachedView = new WeakReference<>(view);
((BasePresenterProtocol) getAttachedView()).onViewAttached();
}
#Override
public void detachView() {
mAttachedView = null;
}
#Override
public boolean viewDoesConform(V view) {
Class<?> klass = view.getClass();
boolean conforms = BasePresenterInterface.BasePresenterProtocol.class.isAssignableFrom(klass);
return conforms;
}
#Override
public boolean viewIsAttached() {
return mAttachedView != null;
}
#Override
public V getAttachedView() {
return mAttachedView.get();
}
#Override
public S getService() {
return mService;
}
#Override
public void setService(S service) {
mService = service;
}
}
I then subclass this into the following:
PhotoRecyclerPresenter:
public class PhotoRecyclerPresenter extends Presenter<PhotoRecyclerPresenter.PhotoRecyclerPresenterProtocol, PhotoService> {
public interface PhotoRecyclerPresenterProtocol extends Presenter.PresenterProtocol {
void onPhotosLoaded(List<TestPhoto> photoList);
void onItemSelected(TestPhoto photo);
void onShowDetail(TestPhoto photo);
}
private static PhotoRecyclerPresenter mSharedInstance;
private PhotoRecyclerPresenter() {
setService(new PhotoService());
}
/**
* External Methods
*/
public void getPhotos() {
boolean noAttachedView = !viewIsAttached();
if (noAttachedView) {
Log.d("DEBUG", "No view attached");
return;
}
getService().getAPI()
.getPhotos()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(photoList -> getAttachedView().onPhotosLoaded(photoList));
}
/**
* Internal Methods
*/
public static PhotoRecyclerPresenter getSharedInstance() {
boolean firstInstance = mSharedInstance == null;
if (firstInstance) {
setSharedInstance(new PhotoRecyclerPresenter());
}
return mSharedInstance;
}
public static void setSharedInstance(PhotoRecyclerPresenter instance) {
mSharedInstance = instance;
}
public void didSelectItem(TestPhoto photo) {
getAttachedView().showDetail(photo);
}
}
And it communicates with the view:
PhotoRecyclerFragment:
public class PhotoRecyclerFragment extends Fragment implements PhotoRecyclerPresenter.PhotoRecyclerPresenterProtocol {
private RecyclerView mRecyclerView;
private RecyclerView.LayoutManager mLayoutManager;
private Activity mParentActivity;
private PhotoRecyclerPresenter mPresenter;
private PhotoRecyclerAdapter mAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_recycler, container, false);
mParentActivity = getActivity();
mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerView);
mLayoutManager = new LinearLayoutManager(mParentActivity);
mAdapter = new PhotoRecyclerAdapter(this);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mAdapter);
mPresenter = PhotoRecyclerPresenter.getSharedInstance();
mPresenter.attachView(this);
return rootView;
}
#Override
public void onDestroyView() {
super.onDestroyView();
mPresenter.detachView();
mAdapter.clear();
}
/**
* PhotoRecyclerPresenterProtocol Methods
*/
#Override
public void onItemSelected(TestPhoto photo) {
mPresenter.didSelectItem(photo);
}
#Override
public void onPhotosLoaded(List<TestPhoto> photoList) {
mAdapter.loadPhotos(photoList);
}
#Override
public void onViewAttached() {
mPresenter.getPhotos();
}
#Override
public void onViewDetached() {
}
#Override
public void onShowDetail(TestPhoto photo) {
Intent detailIntent = new Intent(mParentActivity, PhotoDetailActivity.class);
mParentActivity.startActivity(detailIntent.putExtra(Intent.EXTRA_UID, photo.getPhotoId()));
}
}
This lets me define a set of requirements a view needs to conform to in order to utilize the singleton presenter, while keeping the presenter agnostic about what views use it, as long as they conform to its protocol. So far in my practice project it seems to work fine, but I can't seem to find any resources where what I'm doing is recommended as far as MVP goes, and I have enough self-doubt that I figured I'd ask my first StackOverflow question. Can anyone who has experience with MVP shed some light on this?
Also, if I'm asking in the wrong place, feel free to point me to the correct place to post this.
Thanks :)
From my point of view you are doing the same thing that Mosby does. The only difference is the name of the interface (or protocol in objective-c) world. You call it PresenterProtocol while Mosby call it MvpView. Both are doing the same job: Offering the Presenter an Api of methods the presenter can call to manipulate the view.
The only thing that doesn't make sense is to have a method viewDoesConform(). In Java you have type safety. You can use the generics type V of your Presenter to ensure that your fragment is implementing the Presenter's protocol. just change it to V extends BasePresentersProtocol
Furthermore I think that it doesn't make sense to have a "shared instance" (a.k.a Singleton pattern) of the presenter. I think it would make more sense to have a "shared instance" of the PhotoService. But But please note also that by doing so your code is not testable (unit tests) anymore. You should google for Dependency injection or Inverse of Control to understand how to write modular, reusable and testable code. I'm not talking about dependency injection frameworks like Dagger , spring or guice. You just should understand the idea behind dependency injection. You can write classes following this principle completely without dependency injection frameworks (i.e. using constructor parameters).
Side note: you never unsubscribe your presenter from PhotoService. Depending on how PhotoService is implemented you may have a memory leak because PhotoService observable has a reference to the presenter which prevents the presenter and PhotoService (depending on your concrete implementation) from being garbage collected.
Edit: Mosby defines the protocol for the View. Have a look at the getting started section on the project website. The HelloWorldView defines two methods: showHello() and showGoodbye() (implented by the HelloWorldActivity) and HelloWorldPresenter calls these two methods to manipulate the View. The HelloWorldPresenter also cancels the async requests to avoid memory leaks. You should do that too. Otherwise your presenter can only be garbage collected after the retrofit httpcall has completed.
I've recently gone whole-hog with Dagger because the concept of DI makes complete sense. One of the nicer "by-products" of DI (as Jake Wharton put in one of his presentations) is easier testability.
So now I'm basically using Espresso to do some functional testing, and I want to be able to inject dummy/mock data to the application and have the activity show them up. I'm guessing since, this is one of the biggest advantages of DI, this should be a relatively simple ask. For some reason though, I can't seem to wrap my head around it. Any help would be much appreciated. Here's what I have so far (I've written up an example that reflects my current setup):
public class MyActivity
extends MyBaseActivity {
#Inject Navigator _navigator;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyApplication.get(this).inject(this);
// ...
setupViews();
}
private void setupViews() {
myTextView.setText(getMyLabel());
}
public String getMyLabel() {
return _navigator.getSpecialText(); // "Special Text"
}
}
These are my dagger modules:
// Navigation Module
#Module(library = true)
public class NavigationModule {
private Navigator _nav;
#Provides
#Singleton
Navigator provideANavigator() {
if (_nav == null) {
_nav = new Navigator();
}
return _nav;
}
}
// App level module
#Module(
includes = { SessionModule.class, NavigationModule.class },
injects = { MyApplication.class,
MyActivity.class,
// ...
})
public class App {
private final Context _appContext;
AppModule(Context appContext) {
_appContext = appContext;
}
// ...
}
In my Espresso Test, I'm trying to insert a mock module like so:
public class MyActivityTest
extends ActivityInstrumentationTestCase2<MyActivity> {
public MyActivityTest() {
super(MyActivity.class);
}
#Override
public void setUp() throws Exception {
super.setUp();
ObjectGraph og = ((MyApplication) getActivity().getApplication()).getObjectGraph().plus(new TestNavigationModule());
og.inject(getActivity());
}
public void test_SeeSpecialText() {
onView(withId(R.id.my_text_view)).check(matches(withText(
"Special Dummy Text")));
}
#Module(includes = NavigationModule.class,
injects = { MyActivityTest.class, MyActivity.class },
overrides = true,
library = true)
static class TestNavigationModule {
#Provides
#Singleton
Navigator provideANavigator() {
return new DummyNavigator(); // that returns "Special Dummy Text"
}
}
}
This is not working at all. My Espresso tests run, but the TestNavigationModule is completely ignored... arr... :(
What am I doing wrong? Is there a better approach to mocking modules out with Espresso? I've searched and seen examples of Robolectric, Mockito etc. being used. But I just want pure Espresso tests and need to swap out a module with my mock one. How should i be doing this?
EDIT:
So I went with #user3399328 approach of having a static test module list definition, checking for null and then adding it in my Application class. I'm still not getting my Test injected version of the class though. I have a feeling though, its probably something wrong with dagger test module definition, and not my espresso lifecycle. The reason I'm making the assumption is that I add debug statements and find that the static test module is non-empty at time of injection in the application class. Could you point me to a direction of what I could possibly be doing wrong. Here are code snippets of my definitions:
MyApplication:
#Override
public void onCreate() {
// ...
mObjectGraph = ObjectGraph.create(Modules.list(this));
// ...
}
Modules:
public class Modules {
public static List<Object> _testModules = null;
public static Object[] list(MyApplication app) {
// return new Object[]{ new AppModule(app) };
List<Object> modules = new ArrayList<Object>();
modules.add(new AppModule(app));
if (_testModules == null) {
Log.d("No test modules");
} else {
Log.d("Test modules found");
}
if (_testModules != null) {
modules.addAll(_testModules);
}
return modules.toArray();
}
}
Modified test module within my test class:
#Module(overrides = true, library = true)
public static class TestNavigationModule {
#Provides
#Singleton
Navigator provideANavigator()() {
Navigator navigator = new Navigator();
navigator.setSpecialText("Dummy Text");
return navigator;
}
}
With Dagger 2 and Espresso 2 things have indeed improved. This is how a test case could look like now. Notice that ContributorsModel is provided by Dagger. The full demo available here: https://github.com/pmellaaho/RxApp
#RunWith(AndroidJUnit4.class)
public class MainActivityTest {
ContributorsModel mModel;
#Singleton
#Component(modules = MockNetworkModule.class)
public interface MockNetworkComponent extends RxApp.NetworkComponent {
}
#Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(
MainActivity.class,
true, // initialTouchMode
false); // launchActivity.
#Before
public void setUp() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
RxApp app = (RxApp) instrumentation.getTargetContext()
.getApplicationContext();
MockNetworkComponent testComponent = DaggerMainActivityTest_MockNetworkComponent.builder()
.mockNetworkModule(new MockNetworkModule())
.build();
app.setComponent(testComponent);
mModel = testComponent.contributorsModel();
}
#Test
public void listWithTwoContributors() {
// GIVEN
List<Contributor> tmpList = new ArrayList<>();
tmpList.add(new Contributor("Jesse", 600));
tmpList.add(new Contributor("Jake", 200));
Observable<List<Contributor>> testObservable = Observable.just(tmpList);
Mockito.when(mModel.getContributors(anyString(), anyString()))
.thenReturn(testObservable);
// WHEN
mActivityRule.launchActivity(new Intent());
onView(withId(R.id.startBtn)).perform(click());
// THEN
onView(ViewMatchers.nthChildOf(withId(R.id.recyclerView), 0))
.check(matches(hasDescendant(withText("Jesse"))));
onView(ViewMatchers.nthChildOf(withId(R.id.recyclerView), 0))
.check(matches(hasDescendant(withText("600"))));
onView(ViewMatchers.nthChildOf(withId(R.id.recyclerView), 1))
.check(matches(hasDescendant(withText("Jake"))));
onView(ViewMatchers.nthChildOf(withId(R.id.recyclerView), 1))
.check(matches(hasDescendant(withText("200"))));
}
Your approach doesn't work because it only happens once, and as Matt mentioned, when the activity's real injection code runs, it will wipe out any variables injected by your special object graph.
There are two ways to get this to work.
The quick way: make a public static variable in your activity so a test can assign an override module and have the actual activity code always include this module if it's not null (which will only happen in tests). It's similar to my answer here just for your activity base class instead of application.
The longer, probably better way: refactor your code so that all activity injection (and more importantly graph creation) happens in one class, something like ActivityInjectHelper. In your test package, create another class named ActivityInjectHelper with the exact same package path that implements the same methods, except also plusses your test modules. Because test classes are loaded first, your application will execute with the testing ActivityInjectHelper. Again it's similar to my answer here just for a different class.
UPDATE:
I see you've posted more code and it's close to working, but no cigar. For both activities and applications, the test module needs to be snuck in before onCreate() runs. When dealing with activity object graphs, anytime before the test's getActivity() is fine. When dealing with applications, it's a bit harder because onCreate() has already been called by the time setUp() runs. Luckily, doing it in the test's constructor works - the application hasn't been created at that point. I briefly mention this in my first link.
The call to getActivity will actually start your activity calling onCreate in the process which means you won't be getting your test modules added to the graph in time to be used. Using activityInstrumentationTestcase2 you can't really inject properly at the activity scope. I've worked around this by using my application to provide dependencies to my activities and then inject mock objects into it which the activities will use. It's not ideal but it works. You can use an event bus like Otto to help provide dependencies.
EDIT: the below in post form http://systemdotrun.blogspot.co.uk/2014/11/android-testing-with-dagger-retrofit.html
To test an Activity using Espresso + Dagger I have done the below
Inspired by the answer from #user3399328 I have a DaggerHelper class inside my Application class, which allows the test case to override the #Providers using Test #Modules which supply mocks. As long as
1) This is done before the testCases getActivity() call is made (as my inject call happens in my activity inside Activity.onCreate)
2) tearDown removes the test modules from the object graph.
Examples below.
Note: this is not ideal as this is subject to similar pitfalls of using factory methods for IoC but at least this way its only ever a single call in tearDown() to bring the system under test back to normal.
The DaggerHelper inside my Application class
public static class DaggerHelper
{
private static ObjectGraph sObjectGraph;
private static final List<Object> productionModules;
static
{
productionModules = new ArrayList<Object>();
productionModules.add(new DefaultModule());
}
/**
* Init the dagger object graph with production modules
*/
public static void initProductionModules()
{
initWithModules(productionModules);
}
/**
* If passing in test modules make sure to override = true in the #Module annotation
*/
public static void initWithTestModules(Object... testModules)
{
initWithModules(getModulesAsList(testModules));
}
private static void initWithModules(List<Object> modules)
{
sObjectGraph = ObjectGraph.create(modules.toArray());
}
private static List<Object> getModulesAsList(Object... extraModules)
{
List<Object> allModules = new ArrayList<Object>();
allModules.addAll(productionModules);
allModules.addAll(Arrays.asList(extraModules));
return allModules;
}
/**
* Dagger convenience method - will inject the fields of the passed in object
*/
public static void inject(Object object) {
sObjectGraph.inject(object);
}
}
My Test module inside my test class
#Module (
overrides = true,
injects = ActivityUnderTest.class
)
static class TestDataPersisterModule {
#Provides
#Singleton
DataPersister provideMockDataPersister() {
return new DataPersister(){
#Override
public void persistDose()
{
throw new RuntimeException("Mock DI!"); //just a test to see if being called
}
};
}
}
Test method
public void testSomething()
{
MyApp.DaggerHelper.initWithTestModules(new TestDataPersisterModule());
getActivity();
...
}
Tear down
#Override
public void tearDown() throws Exception
{
super.tearDown();
//reset
MyApp.DaggerHelper.initProductionModules();
}