Generic #BindingConversion doesn't work - android

Why is it not possible to define generic binding conversion within android data binding library?
#BindingConversion
public static <T> T convertMyClass(MyClass<T> obj) {
return obj.get();
}
With this method I am getting can not find the setter for attribute 'android:text' with parameter type com.example.MyClass<java.lang.String> error. Defining explicit types works alright.
I was trying to find the way ObservableField<T> is getting converted but didn't succeed. Does anyone know how is this happening? Is there anything I'm doing wrong?

In two words: type erasure.
Generics are a double edged sword that cuts out some of the run time capability of the type system in exchange for compile time checks. You're telling the compiler to rewrite code to make these type conversions "just work." The trade-off is that it has to turn generic class references like "T" into just "Object". So the signature of your method after compilation is
Object convertMyClass(MyClass)
The data binding system is looking for a return type "String". And so doesn't even consider your method.
The data binding system could probably be made smarter, to be able to recognize your BindingConversion, but I wouldn't hold my breath for that feature.
Here is some bash which illustrates type erasure.
$ echo 'public class A{ public <T> T deRef(java.util.concurrent.atomic.AtomicReference<T> atom) {return atom.get();} }' >A.java
$ javac A.java
$ groovy -e 'println A.class.getMethod("deRef", java.util.concurrent.atomic.AtomicReference.class)'
public java.lang.Object A.deRef(java.util.concurrent.atomic.AtomicReference)
That last line of output is the method signature for the generic method.
A work-around would be to subclass MyClass with specific parameterized subclasses like so:
public class MyStringClass extends MyClass<String> {
#Override
public String get() {
return super.get();
}
#BindingConversion
public static String convertMyClass(MyStringClass obj) {
return obj.get();
}
}
Regarding ObservableField, it doesn't need the BindingConversion mechanism because the data-binding library references it in the java code, and therefore compile-time generics checking does the job of matching types up.

Related

How to create interchangeable class types for android fake testing?

I'm trying to create a fake class for my repository to test a view model.
As far as I understood, the key element here is to create two classes with a common interface so both classes would contain the same methods.
The problem is I get a Type mismatch when trying to initialize an object.
I tried to do the same in a simplified manner:
class fakeClass1 : fakeInterface {
override fun getAllData(): String {
return ""
}}}
class fakeClass2 : fakeInterface {
override fun getAllData(): String {
return ""
}}
interface fakeInterface {
fun getAllData(): String}
val fakeClass: fakeClass1 = fakeClass2()
But that didn't work either.
What am I missing?
Ok, I figured it out.
I was wrong to think that those two classes should be interchangeable.
I solved it by making the ViewModel take the common interface in its constructor instead of the actual repository class. This allows the ViewModel to take any class which implement this interface as it's repository.
I think you worked it out, but just so you're clear (this is an important, fundamental thing!)
val fakeClass: fakeClass1 = fakeClass2()
This is defining a variable called fakeClass that refers to an object with the fakeClass1 type. Then you assign an object with the fakeClass2 type.
But a fakeClass2 is not a fakeClass1, neither is a superclass of the other, so you can't treat one as the other. Your example is simple, but imagine you added coolFunction() to fakeClass1 - they'd now happen to have different structures, and trying to call that method on an object that doesn't have it would cause a crash.
The only thing those classes have in common, is that they both have the fakeInterface type - they are fakeInterfaces, and that guarantees they implement the stuff in that interface (your getAllData function in this case). So if you treat them both as that type instead:
val fakeClass: fakeInterface = fakeClass2()
you can use either one, because they're both fakeInterfaces (similar to how Ints and Doubles are different but they're both Numbers). Because fakeClass is now a fakeInterface, you can only access the functions and properties that a fakeInterface has - you can't call coolFunction() even if you happened to pass in a fakeClass1, because fakeInterface doesn't have that.
(You could cast the variable to fakeClass1, basically saying "oh by the way this object is actually this type as well", but at that point the type system can't guarantee you're correct unless you're explicitly checking fakeClass is fakeClass1, and it'll warn you if that's the case)
The Java tutorials are pretty good and they'll give you an overview about how the types each form a kind of "contract" you work with

What happens if we pass `null` data to a #NonNull annotated object?

I have converted my old java model class to kotlin data class. Some of objects are annotated with #NonNull in java. My question is if null is passed from our backend in my data class what will happen? Does making this username nullable can help in preventing crash if null is passed?
Java code:
public abstract class Comment(){
#NonNull
public abstract String username();
}
Kotlin code:(what happen in this case if null is passed?)
data class Comment(val username: String)
Kotlin code:(it can handle null)
data class Comment(val username: String?)
In java - everything will compile and give a warning
In kotlin - your compiler won't let you pass null to nullable or #notnull annotated type
For example:
public static boolean isUserLoggedIn(#NotNull ctx: Context) {
return ...
}
// Kotlin Invocation
fun main(args: Array<String>) {
isUserLoggedIn(null)
}
And compilation error:
e: C:\_projects\test.kt: (37, 37): Null can not be a value of a non-null type Context
:app:compileDebugKotlin FAILED
FAILURE: Build failed with an exception.
In Java you are able to call this java-method with no compile error but your IDE should show warning (passing null to parameter annotated as #notnull).
Also, in Java you can pass null parameters to notnull kotlin methods. It'll compile and give a warning.
Kotlin supports some of annotations (like JetBrains, Android, Eclipse). The full list can be found here: https://kotlinlang.org/docs/reference/java-interop.html#nullability-annotations
Edit 1 - regarding the comment:
It depends if runtime null check is enabled or not. Kotlin, to ensure null safety in generated code adds kotlin.jvm.internal.Intrinsics.checkNotNull call.
See: https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/jvm/runtime/kotlin/jvm/internal/Intrinsics.java
If value is null NPE will be thrown. So, NPE will be thrown every time null is passed. Every time, even if your code could handle null value.
But, you can disable this check. Then, your code will be small lighter, and also won't throw exception every time null is passed. But you will lose a lot of profits from null safety, and it's also shows that something is bad in your design.
See how: Disable not null checks in Kotlin
Kotlin type system tells a nullable type from a not-nullable type. A declaration like x: String? means null is allowed (same as it was in Java)
The declaration x: String means you do not accept nulls. Kotlin compiler takes care of it and it will try it's best to discard any incorrect code, that tries setting null there.
Kotlin compiler understands annotations like #Nullable or #NotNull: see the documentation for more details
https://kotlinlang.org/docs/reference/java-interop.html#nullability-annotations
Of course, there are ways to call a nun-nullable method with null value (e.g. via Java Reflection or just from another JVM language). To protect from that, Kotlin Compiler emits null checks automatically, and the code will fail-fast.

RxJava - how to get the object from a single without lambdas

I'm completely new to RxJava. I am accessing a method that returns a Single<Location>. I need the Location. This is an Android project using Java 1.7, so no lambdas, which is why I'm stuck. Every example and every book that I see uses lambdas. How do I get this Location from the Single without using lambdas?
locationProvider.getLastKnownLocationWithTimeout() // returns Single<Location>
.flatMap(/* what should go here? */);
Here is the signature for Single<T>.flatMap:
public final <R> Single<R> flatMap(Function<? super T,? extends SingleSource<? extends R>> mapper)
where Function is an interface with exactly one method, apply.
So in your case, I believe you need something like
locationProvider.getLastKnownLocationWithTimeout()
.flatMap(new Function<Location, Single<String>>() {
#Override
public Single<String> apply(Location location) {
// apply transformation to e.g. String here
}
});
where the type String is a placeholder and should be changed based on the transformation you're actually trying to accomplish.
Note that this answer was typed outside an IDE so may be missing a brace or two.

Kotlin type inference faild

When I use both Kotlin and Java in my project
In Java BaseActivity.class:
public abstract <T extends BaseViewModel> Class<T> bindViewModel();
And when I extend BaseActivity In Kotlin :
override fun <T : BaseViewModel<*, out IBaseView<*>>?> bindViewModel(): Class<T> {
return ArchViewModel::class.java
}
the Kotlin remind me the return is type inference faild
Type inference failed. Expected type mismatch:
required:Class<T>
found:Class<ArchViewModel>
How to fix this issue?
P.S. the ArchViewModel.class extends BaseViewModel
Type inference isn't failing. Your method signature says it can return Class<T> for any T (which extends BaseViewModel) you ask for, so it can be called e.g. as
activity.bindViewModel<SomeRandomModel>()
and must return a Class<SomeRandomModel>. Since ArchViewModel::class.java isn't a Class<SomeRandomModel>, the implementation is incorrect.
It's actually impossible to implement correctly whether in Kotlin or in Java. If return ArchViewModel.class compiles in Java, that's because you use the raw type BaseViewModel, so the compiler gives up on typechecking and doesn't report the error.
So you need to fix the method so it can be implemented. How, depends on what you actually want from it.
Alternately, you can "fix" it by casting return ArchViewModel::class.java as Class<T>. The compiler will correctly warn you that this cast is unsafe.

Mock model object without setters

I'm implementing tests on my Android app and I want to do unit tests on a model. The problem is that I do not have setters as the model is created with Realm or by parsing a CSV file (witch univocity csv parser).
So, how could I create a mocked object with valid values? I have something like that:
public class Content {
private String title;
private String description;
...
}
How could I generate a mocked Content object with a title and description data?
Thanks in advance
Use code below in your test class:
Field field = Content.class.getDeclaredField("str");
field.setAccessible(true);
field.set(yourObject, "some value");
yourObject is a instance of Content that you use in your test class.
But you shouldn't fill mock object - you should just define method result for mock object.
A word of warning: reflection has a lot of disadvantages; for example a simple name change for your fields will go unnoticed; and not lead to compiler errors but to failing unit tests later on.
This I suggest a different solution - providing a package-private or protected constructor that you can use to initialize your fields. Then you do not to use reflection; and at the same time, your "public" interface of that class doesn't change either, like:
public class Content {
// unit testing only
Content(String title, ... ) { ...
But of course, you have to do balancing - either you add that constructor that isn't required for production; or you go with not so robust reflection code!

Categories

Resources