What's the right way to evaluate nil returned values from Go functions in Android Java?
Here's what I've tried:
// ExportedGoFunction returns a pointer to a GoStruct or nil in case of fail
func ExportedGoFunction() *GoStruct {
return nil
}
Then I generate a .aar file via gomobile using:
gomobile bind -v --target=android
In my Java code I tried to evaluate nil as null but it's not working. Java Code:
GoLibrary.GoStruct goStruct = GoLibrary.ExportedGoFunction();
if (goStruct != null) {
// This block should not be executed, but it is
Log.d("GoLog", "goStruct is not null");
}
Disclaimer: Other methods from the go library work flawless
For possible future reference, as of 09/2015 I've come up with two ways of dealing with the problem.
The first one is to return an error from the Go code and try/catch-ing the error in Java. Here's an example:
// ExportedGoFunction returns a pointer to a GoStruct or nil in case of fail
func ExportedGoFunction() (*GoStruct, error) {
result := myUnexportedGoStruct()
if result == nil {
return nil, errors.New("Error: GoStruct is Nil")
}
return result, nil
}
And then try/catch the error in Java
try {
GoLibrary.GoStruct myStruct = GoLibrary.ExportedGoFunction();
}
catch (Exception e) {
e.printStackTrace(); // myStruct is nil
}
This approach is both idiomatic Go and Java, but even if it works on preventing the program to crash, it ends up bloating the code with try/catch statements and causing yet more overhead.
So, based on user #SnoProblem answers the non-idiomatic way of solving it and properly handle null values I came up with was:
// NullGoStruct returns false if value is nil or true otherwise
func NullGoStruct(value *GoStruct) bool {
return (value == nil)
}
And then check the code in Java like:
GoLibrary.GoStruct value = GoLibrary.ExportedGoFunction();
if (GoLibrary.NullGoStruct(value)) {
// This block is executed only if value has nil value in Go
Log.d("GoLog", "value is null");
}
Looking through the testing package for go mobile, it looks like you need to cast the null value to the type.
From the SeqTest.java file:
public void testNilErr() throws Exception {
Testpkg.Err(null); // returns nil, no exception
}
Edit: A non-exception example, too:
byte[] got = Testpkg.BytesAppend(null, null);
assertEquals("Bytes(null+null) should match", (byte[])null, got);
got = Testpkg.BytesAppend(new byte[0], new byte[0]);
assertEquals("Bytes(empty+empty) should match", (byte[])null, got);
It may be as simple as:
GoLibrary.GoStruct goStruct = GoLibrary.ExportedGoFunction();
if (goStruct != (GoLibrary.GoStruct)null) {
// This block should not be executed, but it is
Log.d("GoLog", "goStruct is not null");
}
EDIT: Suggestion for utility method:
You could add a utility function to the library to give you the typed nil value.
func NullVal() *GoStruct {
return nil
}
Still a bit hacky, but it should be less overhead than multiple wrappers and exception handling.
Related
Following code works fine when there is data in the Firestore database, but, my app crashes when there is no data with the exception NullPointerException. How can I handle it? This function is called when the user types in a Zip Code. The document name is ZipCode and the user may enter an invalid Zip and that is when the app crashes.
fun getZipDetails(zipCode: String, activity: AddressActivity) {
mFireStore.collection("zip_codes")
.document(zipCode)
.get()
.addOnSuccessListener { document ->
val details = document.toObject(ZipDetails::class.java)!!
activity.successGetZipDetails(details)
}
.addOnFailureListener { e ->
Log.e(
activity.javaClass.simpleName,
"Error while getting user details.",
e
)
}
}
It crashes because you use the Assert operator !! on the result of the toObject method. This method can return null, if the query has no result. Expect this result in your code with either a try catch block, or check if the result of the query exists:
val details = document.toObject(ZipDetails::class.java)
if(details != null) {
activity.successGetZipDetails(details) //you might need to write details!!, I'm not sure
} else {
//handle that this zip code was not valid
}
If you see the documentation of toObject method, it states
Returns the contents of the document converted to a POJO or null if
the document doesn't exist.
So in your case when there is no zip code document.toObject(ZipDetails::class.java) returns null, and then you add non-null assertion (!!), which throws null points exception.
To solve this simply remove the non-null assertion(!!) and update the successGetZipDetails to except nullable parameter as successGetZipDetails(details:ZipDetails?).
Since you are using the Kotlin programming language, a more idiomatic way for handling NullPointerException is to use safe calls:
To perform a certain operation only for non-null values, you can use the safe call operator together with let
In your particular case, that would be:
document.toObject(ZipDetails::class.java)?.let {
activity.successGetZipDetails(it)
}
A function from some SDK is returning me a CompletableFuture. How can I read the value properly once it's reached.
My Code:
CompletableFuture<Web3ClientVersion> web3clientCompletableFuture;
web3clientCompletableFuture = web3jNode.web3ClientVersion().sendAsync();
sendAsync() Code (In SDK):
public CompletableFuture<T> sendAsync() {
return web3jService.sendAsync(this, responseType);
}
I can access the returned data using get(), but that would make the whole process syncronus and block UI.
I've checked the functions signatures on Android API Reference, like:
thenApply(Function<? super T, ? extends U> fn)
handle(BiFunction<? super T, Throwable, ? extends U> fn)
But seems I require some code examples.
[Note: I'm not so familiar with lambda]
Here is a tutorial that has examples that show you how to use these powerful methods of CompletableFuture. You are right you want to use thenApply() if you have to return value after you process the future. But if you simply want to process the future and not return anything, you should use thenAccept() and thenRun(). There are other methods listed with examples as well.
Here is an example that simply returns a CompletableFuture of type integer:
CompletableFuture<Integer> mynumber = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
mynumber = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
return 4 * 4;
});
}
Here arg is the result(CompletableFuture) from the above step, and in your case the data you are receiving from the SDK. You are attaching a callback method(thenApply()) and do what ever you would like to do with it. Depending on your implementation, you can attach multiple thenApply(). Here I am calling a method that will take the result and do some computation with it.
CompletableFuture<Integer> willdoStuff = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
willdoStuff = mynumber.thenApply(arg -> {
compute(arg);
return arg / 2;
});
}
public void compute(int someInt){
Toast.makeText(getApplicationContext(), "The result of the computation is" + someInt, Toast.LENGTH_LONG).show();
}
Just comment out the sleep code to execute this code in the main thread.
The Lambda function is simply input and output, the arguments before {} being the input and the statement within the {}, which is actually a function that does something with the arguments(input). You might want to ask different question in regards to that.
The error that I receive in my Chrome: Inspect log is as below:
"Error: Uncaught (in promise): TypeError: undefined is not a function↵TypeError: undefined is not a function↵ at file:///android_asset/www/build/main.js:101314:24↵ at t.invoke (file:///android_asset/www/build/polyfills.js:3:11562)↵ at Object.inner.inner.fork.onInvoke (file:///android_asset/www/build/main.js:4403:37)↵ at t.invoke (file:///android_asset/www/build/polyfills.js:3:11532)↵ at n.run (file:///android_asset/www/build/polyfills.js:3:6468)↵ at file:///android_asset/www/build/polyfills.js:3:3767↵ at t.invokeTask (file:///android_asset/www/build/polyfills.js:3:12256)↵ at Object.inner.inner.fork.onInvokeTask (file:///android_asset/www/build/main.js:4394:37)↵ at t.invokeTask (file:///android_asset/www/build/polyfills.js:3:12215)↵ at n.runTask (file:///android_asset/www/build/polyfills.js:3:7153)↵ at a (file:///android_asset/www/build/polyfills.js:3:2312)↵ at XMLHttpRequest.invoke (file:///android_asset/www/build/polyfills.js:3:13253)↵ at new Error (native)↵ at Error.d (file:///android_asset/www/build/polyfills.js:3:3991)↵ at l (file:///android_asset/www/build/polyfills.js:3:3244)↵ at file:///android_asset/www/build/polyfills.js:3:3798↵ at t.invokeTask (file:///android_asset/www/build/polyfills.js:3:12256)↵ at Object.inner.inner.fork.onInvokeTask (file:///android_asset/www/build/main.js:4394:37)↵ at t.invokeTask (file:///android_asset/www/build/polyfills.js:3:12215)↵ at n.runTask (file:///android_asset/www/build/polyfills.js:3:7153)↵ at a (file:///android_asset/www/build/polyfills.js:3:2312)↵ at XMLHttpRequest.invoke (file:///android_asset/www/build/polyfills.js:3:13253)"
This happens when I try to run the following
(res) => {
console.log(res);
if (res.receiver) {
///Check if msisdn exists
///If it does, select that item
var found = false;
(<any>Object).values(this.approvedBenifs).forEach(element => {
if (element.msisdn == res.receiver.FlashMsisdn) {
this.selectedBenif = this.approvedBenifs.indexOf(element);
found = true;
}
});
}
However, running this code in Chrome through my Ionic emulator works fine.
The line that seems fishy to me is obviously the one involving (<any>Object). Is the (<any>Object).values() method the problem here, and if so, what strategy can I use to get a collection of values from a specified array?
Edit
I've rewritten the code to be less weird, and it works:
var found = false;
for (let element of this.approvedBenifs) {
if (element.msisdn == res.receiver.FlashMsisdn) {
this.selectedBenif = this.approvedBenifs.indexOf(element);
found = true;
}
}
However, I'd still be interested to know why the (<any>Object).values() method seems to fail on Android.
Perhaps you could try removing the
(<any>Object).values
(I'm asssuming this.approvedBenifs is an array) and just directly iterate through the array.
this.approvedBenifs.forEach(element => {
if (element.msisdn == res.receiver.FlashMsisdn) {
this.selectedBenif = this.approvedBenifs.indexOf(element);
found = true;
}
});
To get a collection of keys just use Object.keys(ARRAY_NAME) which will return an array of keys on the array or object. You're currently fetching the values.
I use the odata4j library to access a WCF Data Service.
This is how I call a Service Method from my Android code:
OQueryRequest<OEntity> l = consumer.getEntities("GetDataList")
.custom("dataId", String.format("'%s'", actualData.ID))
.orderBy("Name").skip(0).top(200);
I checked it with WireShark, and I see that every method call is preceded with 2 calls of metadata information request:
Why? Are they essential? The metadata information is quite heavy, it shouldn't request is every time (not to mention 2 times).
What should I do to prevent odata4j from requesting metadata information so many times?
I found in the source code where the 'extra' request happens (in odata4j/odata4j-core/src/main/java/org/odata4j/consumer/AbstractODataConsumer.java ):
#Override
public EdmEntitySet findEdmEntitySet(String entitySetName) {
EdmEntitySet rt = super.findEdmEntitySet(entitySetName);
if (rt == null && delegate != EdmDataServices.EMPTY) {
refreshDelegate();
rt = super.findEdmEntitySet(entitySetName);
}
return rt;
}
It seems that if the entity set can't be found, the consumer creates an extra roundtrip to the server to get the metadata again (by calling refreshDelegate()):
private void refreshDelegate() {
ODataClientRequest request = ODataClientRequest.get(AbstractODataConsumer.this.getServiceRootUri() + "$metadata");
try {
delegate = AbstractODataConsumer.this.getClient().getMetadata(request);
} catch (ODataProducerException e) {
// to support services that do not expose metadata information
delegate = EdmDataServices.EMPTY;
}
}
I don't quite understand why: maybe it assumes that the server has changed and a new version of the metadata is available so it tries again.
If it fails then it tries to find a function with the given name.
Personally I don't consider this very effective unless the server side is so volatile that it changes between calls.
So, if you have no changing metadata on the server, it is safe to remove the check for the entitySet and let it return as a null:
#Override
public EdmEntitySet findEdmEntitySet(String entitySetName) {
EdmEntitySet rt = super.findEdmEntitySet(entitySetName);
//if (rt == null && delegate != EdmDataServices.EMPTY) {
// refreshDelegate();
// rt = super.findEdmEntitySet(entitySetName);
//}
return rt; //if it is null, then the search for a function will commence
}
In Android, I'd like to write SharedPreferences key-value pairs where the keys are Base64 strings.
// get a SharedPreferences instance
SharedPreferences prefs = getSharedPreferences("some-name", Context.MODE_PRIVATE);
// generate the base64 key
String someKey = new String(Base64.encode("some-key".getBytes("UTF-8"), Base64.URL_SAFE), "UTF-8");
// write the value for the generated key
prefs.edit().putBoolean(someKey, true).commit();
In the last line, the call to commit returns true. So this key-value pair should have been saved successfully.
When I close and destroy the Activity where this piece of code was used and then re-create the Activity (running this code again), the specified value is returned for the key that we used.
But it turns out that, when I destroy the whole application/process (e.g. using "Force stop" in app settings), the value for our key is lost on the next launch of the Activity.
When I don't use Base64.URL_SAFE but Base64.URL_SAFE | Base64.NO_WRAP as the flags for the Base64 encoding, it works fine.
So this problem has been caused by the newlines at the end of the Base64 keys. Keys like abc can be written without any problems. But when the key is abc\n, it fails.
The problem is that it appears to work without problems first, returning true on commit() and returning the correct preference value on subsequent calls. But when the whole application is destroyed and re-started, the value has not been persisted.
Is this intended behaviour? A bug? Does the documentation say anything about valid key names?
I took a look at GrepCode and see that the operations will be the following (I do not mention useless ones) :
android.app.SharedPreferencesImpl.commit()
android.app.SharedPreferencesImpl.commitToMemory()
android.app.SharedPreferencesImpl.queueDiskWrite(MemoryCommitResult,Runnable)
3.1. XmlUtils.writeMapXml(Map, OutputStream)
3.2. XmlUtils.writeMapXml(Map, String, XmlSerializer)
3.3. XmlUtils.writeValueXml(Object v, String name, XmlSerializer ser)
First : how your data are converted ?
The method XmlUtils.writeValueXml writes the Object value in a XML tag with the attribute name set to the String value. This String value contains exactly the value you specified at the SharedPreference's name.
(And I confirmed this by doing a step-by-step debug with your piece of code).
The XML will be with an unescaped line break character. Actually, the XmlSerializer instance is a FastXmlSerializer instance and it does not escape the \n character (see the link for this class at the end if you want to read the source code)
Interesting piece of code :
writeValueXml(Object v, String name, XmlSerializer out) {
// -- "useless" code skipped
out.startTag(null, typeStr);
if (name != null) {
out.attribute(null, "name", name);
}
out.attribute(null, "value", v.toString());
out.endTag(null, typeStr);
// -- "useless" code skipped
}
Second : why the result is true ?
The commit method has the following code :
public boolean commit() {
MemoryCommitResult mcr = commitToMemory();
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
So it returns the mcr.writeToDiskResult which is set in the SharedPreferencesImpl.writeToFile(MemoryCommitResult) method. Interesting piece of code :
writeToFile(MemoryCommitResult mcr) {
// -- "useless" code skipped
try {
FileOutputStream str = createFileOutputStream(mFile);
if (str == null) {
mcr.setDiskWriteResult(false);
return;
}
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
FileUtils.sync(str);
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
try {
final StructStat stat = Libcore.os.stat(mFile.getPath());
synchronized (this) {
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
}
} catch (ErrnoException e) {
// Do nothing
}
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete();
mcr.setDiskWriteResult(true);
return;
} catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
// -- "useless" code skipped
}
As we see at the previous point : the XML writing is "ok" (do not throw anything, do not fails), so the sync in the file will be too (just a copy of a Stream in another one, nothing checks the XML content here !).
Currently : your key was converted to (badly formatted) XML and correctly wrote in a File. The result of the whole operation is true as everything went OK. Your changes are comitted to the disk and in the memory.
Third and last : why do I get back the correct value the first time and a bad one the second time
Take a quick look to what happen when we commit the changes to memory in SharedPreferences.Editor.commitToMemory(...) method (interesting part only... :)):
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
if (v == this) { // magic value for a removal mutation
if (!mMap.containsKey(k)) {
continue;
}
mMap.remove(k);
} else {
boolean isSame = false;
if (mMap.containsKey(k)) {
Object existingValue = mMap.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mMap.put(k, v);
}
mcr.changesMade = true;
if (hasListeners) {
mcr.keysModified.add(k);
}
}
Important point : the changes are commited to the mMap attribute.
Then, take a quick look to how we get back a value :
public boolean getBoolean(String key, boolean defValue) {
synchronized (this) {
awaitLoadedLocked();
Boolean v = (Boolean)mMap.get(key);
return v != null ? v : defValue;
}
}
We are taking back the key from mMap (no reading of the value in the file for now). So we have the correct value for this time :)
When you reload your application, you will load the data back from the disk, and so the SharedPreferencesImpl constructor will be called, and it will call the SharedPreferencesImpl.loadFromDiskLocked() method. This method will read the file content and load it in the mMap attribute (I let you see the code by yourself, link provided at the end).
A step-by-step debug shown me that the abc\n was written as abc (with a whitespace character). So, when you will try to get it back, you will never succeed.
To finish, thank you to #CommonsWare to give me a hint about the file content in the comment :)
Links
XmlUtils
FastXmlSerializer
SharedPreferencesImpl
SharedPreferencesImpl.EditorImpl.commit()
SharedPreferencesImpl.EditorImpl.commitToMemory()
SharedPreferencesImpl.enqueueDiskWrite(MemoryCommitResult, Runnable)
SharedPreferencesImpl.writeToFile(MemoryCommitResult)
SharedPreferencesImpl.loadFromDiskLocked()