Protobuf "Unresolved reference" with generated classes in Android Studio using Kotlin - android

it's my first time diving in with protobuf and jetpack's DataStore but I've encountered an issue which is quite confusing.
I've created my .proto file in src/main/java/proto, it's really simple:
syntax = "proto3";
option java_package = "com.test.appname.proto";
option java_multiple_files = true;
message WalkInfo {
float distance = 1;
bool run = 2;
}
Then I've written in kotlin a serializer class for some data in my app.
object WalkInfoSerializer : Serializer<Walker.WalkInfo>{
override val defaultValue: Walker.WalkInfo
get() = WalkInfo.getDefaultInstance()
override fun readFrom(input: InputStream): Walker.WalkInfo {
try {
return WalkInfo.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override fun writeTo(t: Walker.WalkInfo, output: OutputStream) {
t.writeTo(output)
}
}
I've also set up my build.gradle file like this:
plugins {
id 'com.android.application'
id 'kotlin-android'
id "com.google.protobuf" version "0.8.14"
}
...
dependencies {
...
//DataStore
implementation "androidx.datastore:datastore:1.0.0-alpha05"
implementation 'com.google.protobuf:protobuf-javalite:3.14.0'
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.10.0"
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option 'lite'
}
}
}
}
}
Everything works fine, the classes seem to be generated correctly and they even pop up in android studio's autocomplete.
The problem is that I can't get the project to compile as I can't seem to find a way to resolve the "Unresolved Reference" for the methods generated (like getDefaultInstance(), parseFrom(), writeTo()
Even by writing the full class path it won't work.
Am I missing something? I've tried to play around with build.gradle and the proto file with some settings I found in their documentation but I still couldn't get it to work
Thank you!

Went to sleep, woke up, and knew the answer to my problems.
Dumb brain managed to do 1 + 1 during the night.
I realized that if the proto classes are being generated, I should not be defining them myself in kotlin code.
I had a WalkInfo message that generated a WalkInfo class, but I also had a WalkInfo class already with some methods in it. This is what was confusing the compiler.
After further research I realized that classes generated by proto are not even meant to be extended, they are supposed to be just data containers.
What I ended up doing is renaming my message to WalkInfoStorage while also keeping my WalkInfo kotlin class, I'll then be handling generating WalkInfo instances from the serialized data

Related

Android: Add maven-publish configuration in a separate kotlin dsl script

I've written a .gradle script named publish.gradle which configures publishing {} for releasing my artifact.
Why on a separate script? I have multiple modules and by doing this every releasable module simply defines some variables.
Module build.gradle.kts:
// Module's blah blah
apply(from = "../publish.gradle")
publish.gradle:
apply plugin: 'maven-publish'
publishing {
publications {
// configure release process
}
}
I've recently decided to migrate to Gradle Kotlin DSL. However, there's an issue:
Adding publication {} like this:
plugins {
`maven-publish`
}
publication {
}
Lead to this error:
Expression 'publishing' cannot be invoked as a function. The function 'invoke()' is not found
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public val PluginDependenciesSpec.publishing: PluginDependencySpec defined in org.gradle.kotlin.ds
Which is summarized to
PluginDependenciesSpec is not present as a receiver
What is the difference?
TL; DR
I've added publishing {} config to a separate script which works when in .gradle groovy format but I can not convert to .gradle.kts kotlin format. The publishing is extension of PluginDependenciesSpec class which is not present in the script.
Here's what worked for me:
plugins {
id("maven-publish")
}
configure<PublishingExtension> {
publications.create<MavenPublication>("myPlugin") {
groupId = "com.myCompany.android"
artifactId = "MyPlugin"
version = "1.0.0"
pom.packaging = "jar"
artifact("$buildDir/libs/MyPlugin.jar")
}
repositories {
mavenLocal()
}
}
I understand where you're coming from, converting from groovy to kotlin script is not a simple one to one translation, and most of us, including myself, code by example. In other words, you just need to see a simple example and you can figure out the rest. This works great when examples are readily available. What you need to do when you don't find an example is to turn to the API document. For example, https://docs.gradle.org/current/dsl/org.gradle.api.publish.PublishingExtension.html shows you the available properties for the PublishingExtension and you can keep drilling in to see what properties you have at the next level. This is especially important when examples may be working with an older version and may no longer be applicable. I will say that it wasn't as obvious is that for accessing extensions in kotlin script, requires the configure block. That's a big difference, but I like that approach, because it makes it clearer what the extension properties are a part of. And by the way, kotlin wants double quote, single quotes are no longer acceptable.

DataBinderMapperImpl not adding mapper for module

I'm facing exactly the same problem described here: DataBinding V2 Crashes in multi module android Projects
But there is not any answer on that question.
The problem is that the DataBinderMapperImpl generated class is not being correctly generated:
public class DataBinderMapperImpl extends MergedDataBinderMapper {
DataBinderMapperImpl() {
addMapper(new com.xxx.appmodule.DataBinderMapperImpl());
}
}
If I modify that class and (manually) add a new line for the missing mapper impl:
addMapper(new com.xxx.appmodule.DataBinderMapperImpl());
addMapper(new com.xxx.missingmodule.DataBinderMapperImpl());
Then everything works as expected, but obviously this is not a solution.
Tried with both:
buildFeatures {
dataBinding true
}
// and
dataBinding {
enabled = true
}
But the result is the same.
The most annoying thing is that this behaviour is random. Sometimes the compiler/plugin/??? works ok and the class is correctly generated.
This started to happen just after migrating the project to AndroidX.
Any help would be appreciated!
UPDATE:
This seems to be related to the kotlin-kapt plugin. Currently we are applying that plugin in both module and app build.gradle. If I remove the apply plugin: 'kotlin-kapt' from the module, then the data binding mapper is added in the app's module DataBinderMapperImpl:
#Override
public List<DataBinderMapper> collectDependencies() {
ArrayList<DataBinderMapper> result = new ArrayList<DataBinderMapper>(2);
result.add(new androidx.databinding.library.baseAdapters.DataBinderMapperImpl());
result.add(new com.xxx.missingmodule.DataBinderMapperImpl());
return result;
}
But this has no sense at all for me. What am I missing?

How to access Android's "dynamicFeatures" property from a custom Gradle plugin in buildSrc

In my project, I'd like to generate a class that contains information about my dynamic features. Dynamic features are added this way:
// In the base module’s build.gradle file.
android {
...
// Specifies dynamic feature modules that have a dependency on
// this base module.
dynamicFeatures = [":dynamic_feature", ":dynamic_feature2"]
}
Source: https://developer.android.com/guide/app-bundle/at-install-delivery#base_feature_relationship
I've been searching for solutions since a few days now, and I didn't find much. Currently, my plugin looks like this:
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
if (project == rootProject) {
throw Exception("This plugin cannot be applied to root project")
}
val parent = project.parent ?: throw Exception("Parent of project cannot be null")
val extension = project.extensions.getByName("android") as BaseAppModuleExtension?
?: throw Exception("Android extension cannot be null")
extension.dynamicFeatures
}
}
Unfortunately, extension.dynamicFeatures is empty even if my plugin is applied to the build.gradle file having dynamic features.
It's empty, because you are trying to get extension value at the gradle lifecycle configuration stage, all gradle properties have not configured yet.
Use afterEvaluate closure. In this block dynamicFeatures has already configured and not empty.
project.afterEvaluate {
val extension = project.extensions.getByType(BaseAppModuleExtension::class.java)
?: throw Exception("Android extension cannot be null")
extension.dynamicFeatures
}

Note: Failed to read get kotlin metadata for [Ljava.lang.Object;#79d6c4df

I keep getting this error.
I am working on a project and in the middle of development, I decided to migrate to Android X.
I get the error below:
Note: Failed to read get kotlin metadata for [Ljava.lang.Object;#79d6c4df
There is the same error in a entity file and 4 of the same error in the respective DAO as well.
Here is the code of DAO:
#Dao
public interface FlockDao{
#Query("SELECT * FROM flock_table")
LiveData<List<Flock>> getAllFlocks();
#Query("SELECT * FROM flock_table WHERE fid IN (:flockIds) LIMIT 1")
Flock loadFlockById(int[] flockIds);
#Insert
void insert(Flock flock);
#Update
void update(Flock flock);
#Delete
void delete(Flock flock);
}
And my entity is:
#Entity
public class Flock{
#PrimaryKey(autoGenerate = true)
private int fid;
#ColumnInfo(name = "user_id")
private int uid;
#ColumnInfo(name = "name")
private String name;
#ColumnInfo(name = "capacity")
private int capacity;
#ColumnInfo(name = "type")
private String type;
#ColumnInfo(name = "arrived")
private Date arrived;
.....rest of the code is omitted, there are constructor, setters and getters
}
I updated my Room depency to 2.1.0-alpha05 and got the same problem. Returning to 2.1.0-alpha04 solved mine.
implementation 'androidx.room:room-runtime:2.1.0-alpha04'
annotationProcessor 'androidx.room:room-compiler:2.1.0-alpha04'
UPDATE
If you really want to use Room version 2.1.0-alpha05, add the following depency to your project repository:
maven { url 'https://kotlin.bintray.com/kotlinx/' }
Reference: AndroidX Room Release Notes
UPDATE
I tried 2.1.0-alpha06.
implementation 'androidx.room:room-runtime:2.1.0-alpha06'
annotationProcessor 'androidx.room:room-compiler:2.1.0-alpha06'
Then I add the depency to my project repository,
maven { url 'https://kotlin.bintray.com/kotlinx/' }
There was na error but it compiled. I tested my app in real device for weeks and there wasn’t any issue running my app. My Room database is working fine.
I solved this issue by downgrading to:
implementation 'androidx.room:room-runtime:2.1.0-alpha04'
annotationProcessor 'androidx.room:room-compiler:2.1.0-alpha04'
Solved!
//Downgraded to alpha04.
implementation 'androidx.room:room-runtime:2.1.0-alpha04'
annotationProcessor 'androidx.room:room-compiler:2.1.0-alpha04'
// Other dependencies are..
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0-alpha03'
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.1.0-alpha03'
implementation 'androidx.lifecycle:lifecycle-livedata:2.1.0-alpha03'
annotationProcessor 'androidx.lifecycle:lifecycle-compiler:2.1.0-alpha03'
// Removed this from project level gradle.
maven { url "https://kotlin.bintray.com/kotlinx/" }
Don't forget to Clean & Rebuild the project after these changes
Like most errors that have something to do with Room, the error message that pops up the most is most unlikely to be your problem. For me it helped to raise the max Error count by adding :
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xmaxerrs" << "1000"
}
}
and then executing the gradle task:
:app compileDebugJavaWithJavac
Then you will get a large list of errors, in your case the
Note: Failed to read get kotlin metadata for [Ljava.lang.Object;#79d6c4df
But somewhere in that list are your real errors like a wrong query or something like that.
Fix those errors and rebuild the project, that works most of the time, but sometimes you have to invalidate the cache and restart Android Studio.
Invalidate caches and restart solved my problem. My room version is 2.1.0-alpha06 and I have also add the following dependency to my project repository:
maven { url 'https://kotlin.bintray.com/kotlinx/' }
I got the same error, I updated the room libraries, but issue not fixed,
then I did below steps, ... after that problem solved,
Step 1: Check #DataBase class in your project and check all tables (entities) are inserted
Step 2: Increment version number
Step 3: Add ".fallbackToDestructiveMigration()" before .build().
Don't forget to Clean & Rebuild the project after these changes
As I was typing this answer, I was notified of 2.1.0-alpha07. It resolved all errors. Going back to 2.1.0-alpha06 broke the build again.
It turns out the new version resolved the errors for me.
Before trying to resolve the error, try updating Android Studio first. After updating Gradle, all dependencies, and AS from 3.3.2 to 3.4, I found errors I haven't encountered before, like
error: Room cannot pick a constructor since multiple constructors are suitable. Try to annotate unwanted constructors with #Ignore.
error: Cannot find setter for field.
There are multiple good constructors and Room will pick the no-arg constructor. You can use the #Ignore annotation to eliminate unwanted constructors.
error: Not sure how to convert a Cursor to this method's return type.
Once I fixed them, the build was successful and the app ran. I didn't need to add any additional maven repos mentioned in the accepted answer.
Adding import androidx.room.Dao; and then re-importing it did the trick for me.
I solved by adding this to build.gradle (Module)
androidTestImplementation "androidx.arch.core:coretesting:$rootProject.archLifecycleVersion"
In my build.gradle(Project)
ext {
roomVersion = '2.1.0-alpha06'
archLifecycleVersion = '2.0.0'
}
Just change room_version in your gradle_module file in dependency block and change other room associated libraries you need. Use https://developer.android.com/jetpack/androidx/releases/room#2.2.0-alpha01 as an example. Try to use latest stable version of room.
I have the same error and i used version 2.1.x-alpha
and after updated to 2.2.3
the new version reported the error correctly and it was that one of my entities didn't have primary key
I came across the same problem and fixed it by override the equals & hashCode methods of the object.
A possible solution for your case:
#Override
public int hashCode() { return (27 * fid + (name!= null ? name.hashCode() : 0)); }
And
#Override
public boolean equals(#Nullable Object obj) {
if (this == obj) return true;
if (!(obj instanceof Flock)) return false;
Flock flock = (Flock) obj;
if(fid != flock.fid) return false;
return name != null ? name.equals(flock.name) : flock.name == null;
}
you will also need find a solution for the Date usage in Room, Room doesn't have a build in solution for a Date variable.

How to integrate the Reflections library in android studio using gradle with save and collect

My question is related to the Reflections library by #ronmamo on github and integrating this into my Android project to dynamically access all classes that inherit from a certain interface.
I am not that familiar with gradle or maven so this is a learning process for me but i have reached a roadblock and do not know how to debug / find an answer to this one.
As #ronmamo suggests here, I want to generate a xml file on build containing all scanned metadata and let Reflections collect it later when I use it in my code:
Although scanning can be easily done on bootstrap time of your
application - and shouldn't take long, it is sometime a good idea to
integrate Reflections into your build lifecyle. With simple
Maven/Gradle/SBT/whatever configuration you can save all scanned
metadata into xml/json files just after compile time. Later on, when
your project is bootstrapping you can let Reflections collect all
those resources and re-create that metadata for you, making it
available at runtime without re-scanning the classpath - thus reducing
the bootstrapping time.
I am not sure I fully understand where exactly in the entire process this "bootstrapping" takes place (in terms of the android app lifecycle etc. or even build time?) so I am not certain where exactly to call Reflections.collect(). Currently I am calling it at some point later in my app when the user has reached a certain point in the program.
From several stackoverflow posts and the git readme files, I have come up with this for now: ([...] means removed unrelated code)
build.gradle (Module:app):
dependencies {
[...]
compile 'org.reflections:reflections:0.9.11'
}
build.gradle (Project: MyProject):
buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'org.reflections:reflections:0.9.11'
}
}
allprojects {
repositories {
jcenter()
}
}
task runReflections {
doLast {
org.reflections.Reflections("f.q.n").save("${sourceSet.main.output.classesDir}/META-INF/reflections/myproject-reflections.xml")
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
And later on in my code (this class is reached at some point through user input, not loaded on app start):
Reflections reflections = Reflections.collect();
Set<Class<? extends MyInterface>> allClasses = reflections.getSubTypesOf(MyInterface.class);
This generates the following exception since "reflections" is not instantiated and has the value of "null":
Attempt to invoke virtual method 'java.util.Set org.reflections.Reflections.getSubTypesOf(java.lang.Class)' on a null object reference
I understand that the generated .xml file resides on the computer where the build is happening, and I am not sure if this is also transferred to the android device so my guess is that is why this fails. But at what point does my Java code have access to this file before the apk is transferred and run on my android device?
I have tried googling this in many different ways from different angles but I cannot seem to find a solution to make reflections work in Android. I understand the principle explained here and it seems better to generate the information in an xml file at build time to have the class information available at runtime. But how can I set this up properly?
Thank you
There's a little bit of a chicken-or-egg problem to solve here
You want Reflections API to access the classes compiled from src/main/java
Gradle tasks and the Reflections classes are loaded by Gradle's buildscript classloader
The classes in src/main/java are compiled after the buildscript classloader is defined
You'll need to introduce another classloader that can access the compiled classes to break the cyclic dependency. This can then be passed to Reflections. Eg:
buildscript {
classpath 'org.reflections:reflections:0.9.11'
}
task doReflectyStuff {
dependsOn compileJava
doLast {
URL[] urls = sourceSets.main.runtimeClasspath.files.collect {
it.toURI().toURL()
}
ClassLoader classLoader = new URLClassLoader(urls, null)
Configuration config = new ConfigurationBuilder("com.mypackage", classLoader)
Reflections reflections = new ReflectionsBuilder(config)
...
}
}
See here for a similar question
This is what I did: The task was to use Reflections on Android for classes provided with a dependency (i.e. inside a JAR file). This solution works for me:
top build.gradle:
dependencies {
classpath 'org.reflections:reflections:0.9.10'
}
project build.gradle:
afterEvaluate {
android.applicationVariants.each { variant ->
variant.javaCompiler.doLast {
// get JAR file that contains the classes
def collection = project.configurations.compile*.toURI().find { URI uri -> new File(uri).name.startsWith("startOfJarFileNameHere") }
URL[] urls = collection.collect {
println "Collecting classes using Reflections from " + it
it.toURL()
}
// collect all classes
ClassLoader classLoader = new URLClassLoader(urls, ClassLoader.systemClassLoader)
org.reflections.Configuration config = org.reflections.util.ConfigurationBuilder
.build("package.name.of.interest.here")
.addClassLoader(classLoader)
.setUrls(urls)
org.reflections.Reflections reflections = new org.reflections.Reflections(config)
// save as JSON file into the assets folder
// (a) generate file for current debug or release build
reflections.save(
"${variant.javaCompiler.destinationDir}/../../assets/${variant.buildType.name}/reflections/my-reflections.json",
new org.reflections.serializers.JsonSerializer())
// (b) always update fall-back file for debug (used when running app from Android Studio or IntelliJ)
reflections.save(
"${variant.javaCompiler.destinationDir}/../../../../src/debug/assets/reflections/my-reflections.json",
new org.reflections.serializers.JsonSerializer())
}
}
}
Java code on Android:
InputStream iStream = getAssets().open("reflections/my-reflections.json");
Configuration config = ConfigurationBuilder.build().setSerializer(new JsonSerializer());
Reflections reflections = new Reflections(config);
reflections.collect(iStream);
Set<Class<? extends MyType>> myTypes = reflections.getSubTypesOf(MyType.class);
I have been trying to use Reflections in Android for some days and this is what I have achieved so far. I have created a task in project's build.gradle:
task myTask(dependsOn: compileJava) {
doLast {
URL[] urls = sourceSets.main.runtimeClasspath.files.collect {
it.toURI().toURL()
}
ClassLoader classLoader = new URLClassLoader(urls, ClassLoader.systemClassLoader)
org.reflections.Configuration config = new ConfigurationBuilder()
.addClassLoader(classLoader)
.filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("com.company.project")))
.addScanners(new SubTypesScanner(false))
Reflections reflections = new Reflections(config)
reflections.save("${sourceSets.main.output.classesDirs}/META-INF/reflections/mcommerce-reflections.json", new JsonSerializer())
}
}
Later on a class from the project I instantiate Reflections just as is done in the GitHub's examples (I use Kotlin):
val reflections = Reflections.collect(
"META-INF/reflections",
FilterBuilder().include(".*-reflections.json"),
JsonSerializer()
)
If myTask is run on the Terminal the build is successful but I get this message "given scan urls are empty. set urls in the configuration", I searched for this in Google but didn't find anything helpful.
I tried different ways of configuring Reflections on the gradle file but when I collect them I always receive a null instance.
I hope my answer is of some use for someone.

Categories

Resources