Can we have non public constants in BuildConfig? - android

I am writing a library for Android and wanted it to use the constants in the BuildConfig exclusively - so the lib's client, so to speak, won't see them easily.
So, what I would like to achieve is instead of the public constant like this:
package my.lib;
public final class BuildConfig {
public static final boolean FOO = false;
}
it would rather generate a constant with no access modifier that would make the stuff visible in the package of my lib rather:
package my.lib;
public final class BuildConfig {
static final boolean FOO = false;
}
Is it possible to achieve somehow?
Thanks!

This is the generate() method from BuildConfigGenerator class:
/**
* Generates the BuildConfig class.
*/
public void generate() throws IOException {
File pkgFolder = getFolderPath();
if (!pkgFolder.isDirectory()) {
if (!pkgFolder.mkdirs()) {
throw new RuntimeException("Failed to create " + pkgFolder.getAbsolutePath());
}
}
File buildConfigJava = new File(pkgFolder, BUILD_CONFIG_NAME);
FileWriter out = new FileWriter(buildConfigJava);
JavaWriter writer = new JavaWriter(out);
Set<Modifier> publicFinal = EnumSet.of(Modifier.PUBLIC, Modifier.FINAL);
Set<Modifier> publicFinalStatic = EnumSet.of(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC);
writer.emitJavadoc("Automatically generated file. DO NOT MODIFY")
.emitPackage(mBuildConfigPackageName)
.beginType("BuildConfig", "class", publicFinal);
for (ClassField field : mFields) {
writer.emitField(
field.getType(),
field.getName(),
publicFinalStatic,
field.getValue());
}
for (Object item : mItems) {
if (item instanceof ClassField) {
ClassField field = (ClassField)item;
writer.emitField(
field.getType(),
field.getName(),
publicFinalStatic,
field.getValue());
} else if (item instanceof String) {
writer.emitSingleLineComment((String) item);
}
}
writer.endType();
out.close();
}
}
So this is impossible because BuildConfigGenerator creates only public final modifiers
Set<Modifier> publicFinal = EnumSet.of(Modifier.PUBLIC, Modifier.FINAL);
writer.emitJavadoc("Automatically generated file. DO NOT MODIFY")
.emitPackage(mBuildConfigPackageName)
.beginType("BuildConfig", "class", publicFinal);
and does not give you the option to choose)

Related

Dagger injection for sublclass

I have #inject in a base class and thus all subclasses will inject that dependency from the base class, then I hit an issue where it says "You must explicitly add it to the 'injects' option in one of your modules".
Explicitly adding all subclasses to the injects option does fix the issue but then I will need to make sure whenever I have a new subclass I will have to add the new subclass to "injects", or will get the exception. Is there an easy way to handle that?
Thanks!
If I understand correctly you want to call inject(base activity) but your #Inject annotated fields are in classes sub-classing the base activity.
There is one solution based on reflection (so probably it would break ProGuard). The solution is described in this blog post.
package info.android15.dagger2example;
import java.lang.reflect.Method;
import java.util.HashMap;
public class Dagger2Helper {
private static HashMap<Class<?>, HashMap<Class<?>, Method>> methodsCache = new HashMap<>();
/**
* This method is based on https://github.com/square/mortar/blob/master/dagger2support/src/main/java/mortar/dagger2support/Dagger2.java
* file that has been released with Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ by Square, Inc.
* <p/>
* Magic method that creates a component with its dependencies set, by reflection. Relies on
* Dagger2 naming conventions.
*/
public static <T> T buildComponent(Class<T> componentClass, Object... dependencies) {
buildMethodsCache(componentClass);
String fqn = componentClass.getName();
String packageName = componentClass.getPackage().getName();
// Accounts for inner classes, ie MyApplication$Component
String simpleName = fqn.substring(packageName.length() + 1);
String generatedName = (packageName + ".Dagger" + simpleName).replace('$', '_');
try {
Class<?> generatedClass = Class.forName(generatedName);
Object builder = generatedClass.getMethod("builder").invoke(null);
for (Method method : builder.getClass().getMethods()) {
Class<?>[] params = method.getParameterTypes();
if (params.length == 1) {
Class<?> dependencyClass = params[0];
for (Object dependency : dependencies) {
if (dependencyClass.isAssignableFrom(dependency.getClass())) {
method.invoke(builder, dependency);
break;
}
}
}
}
//noinspection unchecked
return (T)builder.getClass().getMethod("build").invoke(builder);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
private static <T> void buildMethodsCache(Class<T> componentClass) {
if (!Dagger2Helper.methodsCache.containsKey(componentClass)) {
HashMap<Class<?>, Method> methods = new HashMap<>();
for (Method method : componentClass.getMethods()) {
Class<?>[] params = method.getParameterTypes();
if (params.length == 1)
methods.put(params[0], method);
}
Dagger2Helper.methodsCache.put(componentClass, methods);
}
}
public static void inject(Class<?> componentClass, Object component, Object target) {
HashMap<Class<?>, Method> methods = methodsCache.get(componentClass);
if (methods == null)
throw new RuntimeException("Component " + componentClass + " has not been built with " + Dagger2Helper.class);
Class targetClass = target.getClass();
Method method = methods.get(targetClass);
if (method == null)
throw new RuntimeException("Method for " + targetClass + " injection does not exist in " + componentClass);
try {
method.invoke(component, target);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
then in your base activity you can do this:
Dagger2Helper.inject(AppComponent.class, component, this);
I don't think a little bit of reflection would affect performance too much, for me the real issue is breaking ProGuard.

Xamarin Forms - Working with SQLite

I have been trying to follow this video to learn working with SQLite in Xamarin Forms and I am stuck
https://www.youtube.com/watch?v=FVH-9zjRP9M
project.Droid
class LocalDatabaseHelper : Classes.ILocalDatabaseHelper
{
public string GetLocalFilePath(string fileName)
{
string docFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
string libFolder = Path.Combine(docFolder, "..", "Library", "Databases");
if (!Directory.Exists(libFolder))
{
Directory.CreateDirectory(libFolder);
}
return Path.Combine(libFolder, fileName);
}
}
project (pcl)
public static Classes.TaskReminder.TaskReminderDatabaseOperation Database
{
get
{
if (database == null)
{
string LocalFilePath = "";
if(Device.OS==TargetPlatform.Android)
{
LocalFilePath = DependencyService.Get<Classes.ILocalDatabaseHelper>().GetLocalFilePath("TaskReminder.db3");
}
database = new Classes.TaskReminder.TaskReminderDatabaseOperation(LocalFilePath);
}
return database;
}
}
public interface ILocalDatabaseHelper
{
string GetLocalFilePath(string fileName);
}
And it is giving me Unhandled exception when executing
LocalFilePath = DependencyService.Get<Classes.ILocalDatabaseHelper>().GetLocalFilePath("TaskReminder.db3");
please help. thanks in advance.
NOTE (more information):
project (PCL)
public class TaskReminderDatabaseOperation
{
readonly SQLiteAsyncConnection database;
public TaskReminderDatabaseOperation(string dbPath)
{
database = new SQLiteAsyncConnection(dbPath);
database.CreateTableAsync<TaskReminder>().Wait();
}
public Task<List<TaskReminder>> GetTaskRemindersAsync()
{
return database.Table<TaskReminder>().ToListAsync();
}
public Task<TaskReminder> GetTaskReminder(DateTime datetime)
{
return database.Table<TaskReminder>().Where(i => i.ReminderDateTime == datetime).FirstOrDefaultAsync();
}
public Task<int> SaveTaskReminder(TaskReminder taskReminder)
{
if (taskReminder.Id == 0)
{
return database.InsertAsync(taskReminder);
}
else
{
return database.UpdateAsync(taskReminder);
}
}
public Task<int> DeleteTaskReminder(TaskReminder taskReminder)
{
return database.DeleteAsync(taskReminder);
}
}
Look closely at the video, around 29 minutes and 10 seconds. You will see that he adds a attribute to the project class. This registers the dependency with the Dependency Service in Xamarin.Forms.
You probably forgot to do that, that is why you will get a NullReferenceException when you try to resolve the implementation of the interface.
Please look into the workings of the Dependency Service to understand how these kinds of things work and resolve these errors for yourself, instead of just following the video.

How to call method and return its value with UnityPlayer.UnitySendMessage

How can I call this C# method from Java then return the string value?
public string getTest () { return "test"; }
This is what I've tried:
String str = UnityPlayer.UnitySendMessage("ProfileSave", "getTest","");
I am getting the below error
String str=UnityPlayer.UnitySendMessage("ProfileSave", "getTest","");
^
required: String
found: void
UnityPlayer.UnitySendMessage is a void function and does not return a value. Take a look at the UnitySendMessageExtension function implementation below. It is similar to turnipinindia's answer but it returns a value. It only returns a string so you have to convert that string to whatever variable type you are working with.
Java:
public final class MyPlugin
{
//Make class static variable so that the callback function is sent to one instance of this class.
public static MyPlugin testInstance;
public static MyPlugin instance()
{
if(testInstance == null)
{
testInstance = new MyPlugin();
}
return testInstance;
}
string result = "";
public string UnitySendMessageExtension(string gameObject, string functionName, string funcParam)
{
UnityPlayer.UnitySendMessage(gameObject, functionName, funcParam);
string tempResult = result;
return tempResult;
}
//Receives result from C# and saves it to result variable
void receiveResult(string value)
{
result = "";//Clear old data
result = value; //Get new one
}
}
C#:
class TestScript: MonoBehaviour
{
//Sends the data from PlayerPrefs to the receiveResult function in Java
void sendResultToJava(float value)
{
using(AndroidJavaClass javaPlugin = new AndroidJavaClass("com.company.product.MyPlugin"))
{
AndroidJavaObject pluginInstance = javaPlugin.CallStatic("instance");
pluginInstance.Call("receiveResult",value.ToString());
}
}
//Called from Java to get the saved PlayerPrefs
void testFunction(string key)
{
float value = PlayerPrefs.GetFloat(key) //Get the saved value from key
sendResultToJava(value); //Send the value to Java
}
}
Usage from Java:
String str = UnitySendMessageExtension("ProfileLoad", "testFunction","highScore");
This will work with any C# function. Just make sure that you call sendResultToJava function at the end of the C# function like we did in the testFunction function, if you want it to return value.
UnitySendMessage cannot return anything to Android.
One way you could do this is implement another method in Android that Unity will pass the value back to when requested.
So for example, first in android you request the data:
UnityPlayer.UnitySendMessage("ProfileSave", "getData");
In unity you have a method that receives that request and sends the data back to android:
public void getData () {
string dataString = "exampleData";
//code to send data back to ReceiveData method in Android, something like this probably:
AndroidJavaClass myAndroidClass = new AndroidJavaClass ("com.myCompany.myApplication.myClass");
myAndroidClass.CallStatic ("ReceiveData",dataString);
}
Then in android you have a method waiting to receive the data (most likely a static method):
public static void ReceiveData(string data){
//do stuff with data
}

Android - Simple XML Framework. #Convert interferes with #Attribute - How to solve this?

I was working on capturing the order of elements contained in tag. Here is all the code:
League.java:
#Root
#Convert(value = LeagueConverter.class)
public class League
{
#Attribute
private String name;
#Element(name="headlines", required = false)
private Headlines headlines;
#Element(name="scores", required = false)
private Scores scores;
#Element(name="standings", required = false)
private Standing standings;
#Element(name="statistics", required = false)
private LeagueStatistics statistics;
public List<String> order = new ArrayList<String>();
// get methods for all variables
}
LeagueConverter.java:
public class LeagueConverter implements Converter<League>
{
#Override
public League read(InputNode node) throws Exception
{
League league = new League();
InputNode next = node.getNext();
while( next != null )
{
String tag = next.getName();
if(tag.equalsIgnoreCase("headlines"))
{
league.order.add("headlines");
}
else if(tag.equalsIgnoreCase("scores"))
{
league.order.add("scores");
}
else if(tag.equalsIgnoreCase("statistics"))
{
league.order.add("statistics");
}
else if(tag.equalsIgnoreCase("standings"))
{
league.order.add("standings");
}
next = node.getNext();
}
return league;
}
#Override
public void write(OutputNode arg0, League arg1) throws Exception
{
throw new UnsupportedOperationException("Not supported yet.");
}
}
Exampe of XML:
<android>
<leagues>
<league name ="A">
<Headlines></Headlines>
<Scores></Scores>
...
</league>
<league name ="B">...</league>
</leagues>
</android>
How I'm calling it and expecting it to behave: (Snippet)
Android android = null;
Serializer serial = new Persister(new AnnotationStrategy());
android = serial.read(Android.class, source);
Log.i("Number of leagues found ",tsnAndroid.getLeagueCount() + ""); // prints fine
League nhl = tsnAndroid.getLeagues().get(0); // works fine
// DOES NOT WORK throws NullPointerEx
League nhl2 = tsnAndroid.getLeagueByName("A");
// DOES NOT WORK throws NullPointerEx
for(String s : nhl.getOrder())
{
Log.i("ORDER>>>>>", s);
}
The problem:
android.getLeagueByName() (Works with #Attribute name) suddenly stops working when I have the converter set, so its like the following from League.java, never gets set.
#Attribute
private String name; // not being set
However, when I comment out the converter declaration in League.java - Every league has an attribute called name and android.getLeagueByName() starts working fine...
Does #Convert for League somehow interfere with #Attribute in League?
Even though this question is outrageously old (as is the SimpleXML library), I will give my two cents.
#Convert annotation works only with #Element, but it does not have any effect on #Attribute. I'm not sure if that's a bug or a feature, but there is another way of handling custom serialized objects - called Transform with Matcher, and it works both with Attributes and with Elements. Instead of using the Converters, you define a Transform class that handles serialization and deserialization:
import java.util.UUID;
import org.simpleframework.xml.transform.Transform;
public class UUIDTransform implements Transform<UUID> {
#Override
public UUID read(String value) throws Exception {
return value != null ? UUID.fromString(value) : null;
}
#Override
public String write(UUID value) throws Exception {
return value != null ? value.toString() : null;
}
}
As you can see, it is more straight-forward than implementing the Convert interface!
Create a similar class for all your objects that require custom de/serialization.
Now instantiate a RegistryMatcher object and register there your custom classes with their corresponding Transform classes. This is a thread-safe object that internally uses a cache, so it might be a good idea to keep it as a singleton.
private static final RegistryMatcher REGISTRY_MATCHER = new RegistryMatcher();
static {
try {
REGISTRY_MATCHER.bind(UUID.class, UUIDTransform.class);
// register all your Transform classes here...
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Finally, you can create a Persister class each time before a conversion and pass it the AnnotationStrategy together with your RegistryMatcher instance. In this factory method below, we will also use an indenting formatter:
private static Persister createPersister(int indent) {
return new Persister(new AnnotationStrategy(), REGISTRY_MATCHER, new Format(indent));
}
Now you can make your serialization/deserialization methods:
public static String objectToXml(Object object, int indent) throws MyObjectConversionException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Persister p = createPersister(indent);
try {
p.write(object, out, "UTF-8");
return out.toString("UTF-8");
} catch (Exception e) {
throw new MyObjectConversionException("Cannot serialize object " + object + " to XML: " + e.getMessage(), e);
}
}
public static <T> T xmlToObject(String xml, final Class<T> clazz) throws MyObjectConversionException {
Persister p = createPersister(0);
try {
return (T) p.read(clazz, xml);
} catch (Exception e) {
throw new MyObjectConversionException(
"Cannot deserialize XML to object of type " + clazz + ": " + e.getMessage(), e);
}
}
The only issue with this approach is when you want to have different formatting for the same object - e.g. once you want the java.util.Date to have just the date component, while later on you also want to have the time component. Then just extend the Date class, calling it DateWithTime, and make a different Transform for it.
#ElementListUnion will capture the order of elements
The #Convert annotation works only on #Element fields. I am struggling against converting #Attribute fields too but with no success for now...

Gson IllegalStateException

I'm a noob when it comes to basically all forms of storage aside from SharedPreferences and some SQLite. I did some searching and found that JSON+GSON was a fast way to parse Objects and their fields into storable Strings.
So, in my game, I have a Player object which has fields that are also my own classes:
public class Player {
private int something_game_related = 1;
private Skill equipped_skill;
private Item equipped_weapon;
public Player () {}
}
I suspect those classes are the problem, because when I try to run a simple save method:
private class ItemSerializer implements JsonSerializer<Item> {
public JsonElement serialize( Item src, Type typeOfSrc, JsonSerializationContext context ) {
return new JsonPrimitive(src.toString());
}
}
private class SkillSerializer implements JsonSerializer<Skill> {
public JsonElement serialize( Skill src, Type typeOfSrc, JsonSerializationContext context ) {
return new JsonPrimitive(src.toString());
}
}
public void doSave() {
GsonBuilder gson = new GsonBuilder();
//Both custom classes have zero-arg constructors so we don't need to register those
gson.registerTypeAdapter( Item.class, new ItemSerializer() );
gson.registerTypeAdapter( Skill.class, new SkillSerializer() );
Gson g = gson.create();
String mPlayer = "";
Type player = new TypeToken<Player>(){}.getType();
try{
mPlayer = g.toJson( GameView.mPlayer, player );
}
catch (Exception e) {e.printStackTrace();}
}
I get this Exception: java.lang.IllegalStateException: How can the type variable not be present in the class declaration!
My Question is..
How do I get these custom serializers to work? Like I said, I'm a noob.. but it looks like I did it right..
In the docs it says (kind of in the fine print) that static fields are excluded: http://sites.google.com/site/gson/gson-user-guide#TOC-Excluding-Fields-From-Serialization
You can do something like "excludeFieldsWithModifier(Modifier.STATIC)" in the GSON builder to include them.

Categories

Resources