When using the retrofit library, you create an interface that contains functions with all your endpoints but you never implement it anywhere. My question is where is this interface implemented? I have noticed the same pattern when creating a DAO when using android Room
They use two different approaches, Retrofit takes advantage of Java reflection proxy which is a tool to implement interfaces in runtime, your proxy's invoke method will retrieve the called method scheme through reflection and can work based on that. In fact retrofit can work with method metadata only.
Proxy is a way to create objects from interfaces without implementing them using code, but by a single invoke method which simply gets these arguments
#Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Log.d("proxyCall", method.getName());
return Response (...);
}
You can get other info from method as well (like it's annotations e.g. #GET etc.)
On the other hand, Room uses annotation processors to generate code during compile time (you add it's processor a.k.a. compiler in the gradle configuration using annotationProcessor or kapt).
You can find the Room's generated source codes inside the build folder of your module.
Retrofit is used the Java Dynamic Proxy approach to create classes at runtime, and this requires an interface so you just need to define your Interface and retrofit will build a type-safe implementation of your interface at runtime.
Room Database uses the Annotation Processor and Reflection simultaneously. It uses Annotation Processor to generate Java Code for classes annotated as #DAO and #Database and generates implementation class with _Impl suffix.
It uses reflection to find the Database implementation class. When you call Room.databaseBuilder(context, AppDatabase.class, databaseName).build();It will call like this with the value of suffix being _Impl
#NonNull
static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {
final String fullPackage = klass.getPackage().getName();
String name = klass.getCanonicalName();
final String postPackageName = fullPackage.isEmpty()
? name
: (name.substring(fullPackage.length() + 1));
final String implName = postPackageName.replace('.', '_') + suffix;
//noinspection TryWithIdenticalCatches
try {
#SuppressWarnings("unchecked")
final Class<T> aClass = (Class<T>) Class.forName(
fullPackage.isEmpty() ? implName : fullPackage + "." + implName);
return aClass.newInstance();
} catch (ClassNotFoundException e) {
throw new RuntimeException("cannot find implementation for "
+ klass.getCanonicalName() + ". " + implName + " does not exist");
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access the constructor"
+ klass.getCanonicalName());
} catch (InstantiationException e) {
throw new RuntimeException("Failed to create an instance of "
+ klass.getCanonicalName());
}
}
Room has limited the use of reflection so the speed of the room is not affected much by reflection
Related
I was modifying the libcore on Andorid for debugging purpose.
It took a lot of time to build even for a variable change.
Is it possible to pass the data to libcore of android?
(so I can change the data in the running time).
I tried System.getProperty() but the data could not cross process.
I also tried SystemProperties.get() but it seems it can not be used in libcore (it could not find the package and symbol).
Does anyone know how to pass data to the libcore on Android?
Thanks Nativ.
JNI is doable but a little complicated for me.
Finally, I used a simple, easy but stupid way to do that.
I created a file and saved my parameter in this file, and get the data from libcore.
It is a stupid way but worked for me for debugging.
Now I don't need to rebuild libcore and It saved much for me.
You can use reflection on class android.os.SystemProperties to get System Properties at runtime.
Code example:
public static String getSystemProperty(String key) {
String value = "";
try {
Class clazz = Class.forName("android.os.SystemProperties");
if (clazz != null) {
Object object = clazz.newInstance();
value = (String) (clazz.getMethod("get", String.class).invoke(object, key));
} else {
System.err.println(TAG + ", getSystemProperty: Class is null.");
}
} catch (Exception e) {
e.printStackTrace();
}
return value;
}
I would like to take a look at an object, similar to a print_r(an_object) in php, or a console.log(an_object) in javascript (in browser), but for Android.
I tried this
public void a_method( SomeClass the_arg )
{
Log.d( "an_id" , the_arg );
}
This generates an error message:
Error:(23, 10) error: no suitable method found for d(String,View)
method Log.d(String,String,Throwable) is not applicable
(actual and formal argument lists differ in length)
method Log.d(String,String) is not applicable
(actual argument View cannot be converted to String by method invocation conversion)
You cannot print an object to the console in Java as you would in javascript.
You have three solutions.
1) Use debugger. Place a breakpoint and debug in android studio. Then you can inspect the full object in scope.
2) Serialize your object (for example to JSON) and print the result to console.
3) Override toString method of your object to give you all the information you want and call Log.d("myTag", yourObj.toString())
I highly recommend first method. I used to avoid Debugger but learning how to use the debugger was the best thing I did. It increases your efficiency and makes debugging super easy
Convert object to JSON. You can use Gson.
val gson = Gson()
val json = gson.toJson(yourObject)
Log.e(TAG, json)
Or
Log.e(TAG, Gson().toJson(yourObject))
The second argument must be a String.
Log.d("an_id", String.valueOf(the_arg));
Your error says you can't log a View class
no suitable method found for d(String,View)
Don't be surprised when you see some nonsense in the console when you print that View object through using String.valueOf
It's a bit easy to output the data object in the logcat.
The correct way is to override the toString() inside the object.
It can be generated by the Android Studio itself by following this simple steps:
1- Open the DataClass
2- Press Alt+Insert OR right click and click on Generate...
3- In Generate window select toString()
4- Select all the variables in the next window and click OK
5- toString() method would override in your data class returning the String template of your data model.
Cheers!
just wrote one generic method, which makes it possible trough reflection:
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (Field field : this.getClass().getDeclaredFields()) {
String item;
if(! field.getType().getSimpleName().equals("ArrayList")) {
try {
Object value = field.get(this);
item = String.format("%s %s %s: %s%n", Modifier.toString(field.getModifiers()), field.getType().getSimpleName(), field.getName(), String.valueOf(value));
sb.append(item);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} else {
item = String.format("%s %s %s: ArrayList<>%n", Modifier.toString(field.getModifiers()), field.getType().getSimpleName(), field.getName());
sb.append(item);
}
}
return sb.toString();
}
You can try using Gson class as shown below :
public void yourMethod(SomeClass theObject) {
Gson gson = new Gson();
Log.d( "sample" , gson.toJson(theObject));
}
Here is its Gradle repository
implementation 'com.google.code.gson:gson:2.8.6'
Is there a way inside of Android Java to create an Entity Key?
For example, inside the Cloud Endpoints Java module code you can do this:
Key<User> userKey= Key.create(User.class, userId);
or with an Ancestor:
Key<Post> postKey= Key.create(userKey, Post.class, postId);
How can you do the above in the Android generated client library? I want to see if I can create a key in Android and pass it to an API method (probably as a websafeKey userKey.getString()).
BONUS: How can you do this with the objective-C Cloud Endpoints client library?
I doubt you want either the datastore nor objectify code in your Android App. That simply not where that belongs. So the way to go is to look at the source of the KeyFactory. In the method keyToString() we can see that most of the magic happens in the class KeyTranslator in method convertToPb().
Here's the code of convertToPb:
public static Reference convertToPb(Key key) {
Reference reference = new Reference();
reference.setApp(key.getAppId());
String nameSpace = key.getNamespace();
if (!nameSpace.isEmpty()) {
reference.setNameSpace(nameSpace);
}
Path path = reference.getMutablePath();
while (key != null) {
Element pathElement = new Element();
pathElement.setType(key.getKind());
if (key.getName() != null) {
pathElement.setName(key.getName());
} else if (key.getId() != Key.NOT_ASSIGNED) {
pathElement.setId(key.getId());
}
path.addElement(pathElement);
key = key.getParent();
}
Collections.reverse(path.mutableElements());
return reference;
}
And here's the code of keyToString()
public static String keyToString(Key key) {
if (!key.isComplete()) {
throw new IllegalArgumentException("Key is incomplete.");
} else {
Reference reference = KeyTranslator.convertToPb(key);
return base64Url().omitPadding().encode(reference.toByteArray());
}
}
Now what you want to do, is to replace the Key stuff in convertToPb with "normal" parameters (type, name/key, parent type, parent name/key) and thus rewrite the method to create a websafeKey without an actual Key object.
It would be much easier though if your app engine API simply accepted the ids and you'd recreate the key on the appengine side of things. My APIs are usually structured like
/user/<userId>/post/<postId>
if i assume an Entity that looks like this
#Entity public class Post {
#Parent Ref<User> user
#Id id; }
Regarding the bonus: What (the heck) is an Objectify Cloud Endpoint? I know Cloud Endpoints and Objectify, but i have not heard of a product that combines the two.
I'm using Square's Tape library and i've run in to a requirement where i basically need to have an abstract TapeTask class. The problem though is that the deserialization process for the GsonConverter (which implements the library's FileObjectQueue.Converter - as demonstrated in the sample project) doesn't play well with interfaces/abstract classes.
I thought it was a Gson deserialization problem so i registered my Gson instance with a custom TypeAdapter, but that still doesn't do the trick. I figure it has something to do with the FileObjectQueue.Converter.
I'm currently trying to work around this problem, with a nasty wrapper callback interface from my sub-tasks.
My requirement is to have a single TapeQueue and be able to send in multiple types of tasks. So I have a TapeTask abstract class and have concrete implementations like ImageDownloadTask, ImageUploadTask, UrlPostWithRetrofitTask, GoogleAnalyticsTrackerTask ... etc. all going in to a single queue.
Is there a way to achieve this. I guess my question boils down to:
What do i need to do to make the FileObjectQueue.Converter play well with abstract classes?
hint :P : The javadoc for that class says "..you need to also include the concrete class name in the serialized byte array" but i'm not sure what that means. If anyone could post an explanation of how the name can be included in the serialized byte array, in a way that achieves my purpose, i'd be grateful!
I went ahead and wrote an Abstract Gson Convertor. I don't think it's super-efficient but gets the job done:
/**
* Use GSON to serialize classes to a bytes.
*
* This variant of {#link GsonConverter} works with anything you throw at it.
* It is however important for Gson to be able to understand your inner complex objects/entities
* Use an {#link InterfaceAdapter} for these purposes.
*
*/
public class GsonAbstractClassConverter<T>
implements FileObjectQueue.Converter<T> {
public static final String CONCRETE_CLASS_NAME = "concrete_class_name";
public static final String CONCRETE_CLASS_OBJECT = "concrete_class_object";
private final Gson _gson;
public GsonAbstractClassConverter(Gson gson) {
_gson = gson;
}
#Override
public T from(byte[] bytes) {
Reader reader = new InputStreamReader(new ByteArrayInputStream(bytes));
JsonObject completeAbstractClassInfoAsJson = _gson.fromJson(reader, JsonObject.class);
Class<T> clazz;
try {
String className = completeAbstractClassInfoAsJson.get(CONCRETE_CLASS_NAME).getAsString();
clazz = (Class<T>) Class.forName(className);
} catch (ClassNotFoundException e) {
Timber.e(e, "Error while deserializing TapeTask to a concrete class");
return null;
}
String objectDataAsString = completeAbstractClassInfoAsJson.get(CONCRETE_CLASS_OBJECT)
.getAsString();
return _gson.fromJson(objectDataAsString, clazz);
}
#Override
public void toStream(T object, OutputStream bytes) throws IOException {
Writer writer = new OutputStreamWriter(bytes);
JsonObject completeAbstractClassInfoAsJson = new JsonObject();
completeAbstractClassInfoAsJson.addProperty(CONCRETE_CLASS_NAME, object.getClass().getName());
completeAbstractClassInfoAsJson.addProperty(CONCRETE_CLASS_OBJECT, _gson.toJson(object));
_gson.toJson(completeAbstractClassInfoAsJson, writer);
writer.close();
}
}
I'm using reflection to call a method that is outside of the target API level of my Android application:
try {
Method m = Class.forName("android.content.Context")
.getDeclaredMethod("getExternalCacheDir");
Object result = m.invoke(this);
if (result instanceof File) {
Log.v("MyApp", "external cache: "
+ ((File) result).getAbsolutePath());
cacheDirectory = (File) result;
} else {
Log.v("MyApp", "non-file cache: " + result);
}
} catch (Exception e) {
// ...
}
I can optimize this without any problems through Proguard, but it warns me:
Note: com.example.MyApp accesses a declared method 'getExternalCacheDir()' dynamically
Maybe this is library method 'android.content.Context { java.io.File getExternalCacheDir(); }'
Maybe this is library method 'android.content.ContextWrapper { java.io.File getExternalCacheDir(); }'
Maybe this is library method 'android.test.mock.MockContext { java.io.File getExternalCacheDir(); }'
Note: there were 1 accesses to class members by means of introspection.
You should consider explicitly keeping the mentioned class members
(using '-keep' or '-keepclassmembers').
Is this an actual problem, or is Proguard just informing me of a potential problem?
This is similar to the answer Paul Lammertsma posted. Also take a look at the author of ProGuard Eric Lafortune's answer at: How to suppress 'Maybe this is program method' warnings from ProGuard
You can avoid it by explicitly mentioning the method in the
configuration:
-keep class com.foo.OtherClass { com.foo.OtherClass getInstance(); }
Alternatively, you can suppress notes on a class:
-dontnote com.foo.MyClass
The problem resided in a library project that I was using, and the proguard.cfg from that project wasn't being inspected by Proguard.
By adding the following lines to my own projects proguard.cfg, I was able to make the notice disappear:
-keep class android.content.Context {
public java.io.File getExternalCacheDir();
}