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.
Related
I'm writing an embedded system which doesn't have an internet connection, so the main interaction is using BLE from an Android device. (ESP32 is using the NimBLE-Arduino library)
I have some write characteristics and some read characteristics. Each one individually works well, but when I try to read immediately after write (or vice versa), only the first callback in the ESP32 is called
ESP32 code
// Setup function
void Bluetooth::setupCharacteristic()
{
NimBLEService *pService = m_pServer->createService(SERVICE_UUID);
NimBLECharacteristic *getStationsChr = pService->createCharacteristic(GET_STATIONS_CHR_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::READ_AUTHEN);
NimBLECharacteristic *setTimeChr = pService->createCharacteristic(SET_TIME_CHR_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_ENC | NIMBLE_PROPERTY::WRITE_AUTHEN);
getStationsChr->setCallbacks(this);
setTimeChr->setCallbacks(this);
pService->start();
}
// Read/Write callbacks
void Bluetooth::onRead(NimBLECharacteristic* pCharacteristic){
log_i("%s", pCharacteristic->getUUID().toString().c_str());
log_i(": onRead(), value: %s",pCharacteristic->getValue().c_str());
if (m_pCallback)
{
auto value = pCharacteristic->getValue();
//m_pCallback->onMessageReceived(&value);
}
...
}
void Bluetooth::onWrite(NimBLECharacteristic* pCharacteristic) {
std::string characteristicStr = pCharacteristic->getUUID().toString();
std::string value_str(pCharacteristic->getValue().c_str());
log_i("%s: onWrite(), value: %s (%s)", characteristicStr.c_str(), pCharacteristic->getValue().c_str(), value_str.c_str());
// Process incoming value and call the callback
// ...
if (m_pCallback)
{
auto value = pCharacteristic->getValue();
//m_pCallback->onMessageReceived(&value);
}
...
}
Android code
private fun updateTime() {
val calendar = GregorianCalendar()
val ts = Instant.now().epochSecond
val timeZone = timezonesMap?.get(calendar.timeZone.id) ?: ""
val timeMessage = TimeMessage(ts, 0, timeZone)
val setTimeChar = bluetoothGatt?.getService(SERVICE_UUID)?.getCharacteristic(SET_TIME_UUID)
if (setTimeChar?.isWritable() == true) {
val timeStr = gson.toJson(timeMessage)
Log.d("updateTime", "setting time to $timeStr")
sendToCharacteristic(setTimeChar, timeStr)
}
}
// This function is used to break long message to multiple messages, this is a simplified version
// for brevity
private fun sendToCharacteristic(characteristic: BluetoothGattCharacteristic?, value: String)
{
Log.d(TAG, "Sending to write characteristic ${characteristic?.uuid} value ($value)")
characteristic?.value = value.toByteArray()
bluetoothGatt?.writeCharacteristic(characteristic)
TimeUnit.MILLISECONDS.sleep(500)
}
private fun getStations() {
Log.d("getStations", "getting stations")
val getStationsChar = bluetoothGatt?.getService(SERVICE_UUID)?.getCharacteristic(GET_STATIONS_UUID)
if (getStationsChar?.isReadable() == true) {
Log.d("getStations", "reading")
bluetoothGatt?.readCharacteristic(getStationsChar)
}
}
The issue is when calling my sync() function
fun sync() {
Log.d("sync", "syncing")
bluetoothGatt?.apply {
if (device.bondState != BluetoothDevice.BOND_BONDED) {
device.createBond()
}
else {
updateTime()
// getStations()
}
}
}
If I uncomment the getStations, the read callback isn't called. If I comment the updateTime and uncomment the getStations, the read callback is called. switching the order doesn't make a difference. First called function works, second doesn't. Also adding a 1 second delay between the calls doesn't work
Your problem lies on the Android side.
In GATT you may only have one outstanding request. This means you must wait for the previous callback before executing a new operation. In particular, you must in this case wait for onCharacteristicWrite on your BluetoothGattCallback object after performing a write operation, before you can execute a next operation.
Adding a sleep doesn't work since you block the thread and hence prevent the result callback from running, whenever a response is received from the remote device.
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
}
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'
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.
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()