diff --git a/build.gradle b/build.gradle index 77fec39..d9ab50e 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.2' + classpath 'com.android.tools.build:gradle:3.5.2' classpath 'com.novoda:bintray-release:0.9.1' } } diff --git a/graphenej/src/main/java/cy/agorise/graphenej/AssetAmount.java b/graphenej/src/main/java/cy/agorise/graphenej/AssetAmount.java index 37d087a..71b24c9 100644 --- a/graphenej/src/main/java/cy/agorise/graphenej/AssetAmount.java +++ b/graphenej/src/main/java/cy/agorise/graphenej/AssetAmount.java @@ -27,7 +27,7 @@ import cy.agorise.graphenej.interfaces.JsonSerializable; /** * Class used to represent a specific amount of a certain asset */ -public class AssetAmount implements ByteSerializable, JsonSerializable { +public class AssetAmount implements ByteSerializable, JsonSerializable, Comparable { /** * Constants used in the JSON serialization procedure. */ @@ -196,6 +196,14 @@ public class AssetAmount implements ByteSerializable, JsonSerializable { return String.format("(asset=%s, amount=%s)", asset.getObjectId(), amount.toString(10)); } + @Override + public int compareTo(AssetAmount other) { + if(!other.asset.equals(this.asset)) throw new AssertionError("Cannot compare amounts of different assets"); + if(this.amount == null) throw new NullPointerException("Asset amount field is null"); + if(other.amount == null) throw new NullPointerException("Asser amount field is null"); + return amount.compareTo(other.amount); + } + /** * Custom serializer used to translate this object into the JSON-formatted entry we need for a transaction. */ diff --git a/graphenej/src/main/java/cy/agorise/graphenej/api/ApiCallback.java b/graphenej/src/main/java/cy/agorise/graphenej/api/ApiCallback.java new file mode 100644 index 0000000..188711a --- /dev/null +++ b/graphenej/src/main/java/cy/agorise/graphenej/api/ApiCallback.java @@ -0,0 +1,28 @@ +package cy.agorise.graphenej.api; + +import javax.annotation.Nullable; + +import cy.agorise.graphenej.models.JsonRpcResponse; +import okhttp3.Response; + +/** + * Interface defining the basic contract an API request can expect. + */ +public interface ApiCallback { + + /** + * Method called whenever we have a successful response from the node. + * + * @param response Parsed response + * @param text Raw text response + */ + void onResponse(JsonRpcResponse response, String text); + + /** + * Method called whenever there was an error and the response could not be delivered. + * + * @param t Error Throwable, potentially with some details about the problem. + * @param response Node response, if any + */ + void onFailure(Throwable t, @Nullable Response response); +} diff --git a/graphenej/src/main/java/cy/agorise/graphenej/api/android/NetworkService.java b/graphenej/src/main/java/cy/agorise/graphenej/api/android/NetworkService.java index 5e68703..6fcc810 100644 --- a/graphenej/src/main/java/cy/agorise/graphenej/api/android/NetworkService.java +++ b/graphenej/src/main/java/cy/agorise/graphenej/api/android/NetworkService.java @@ -1,5 +1,7 @@ package cy.agorise.graphenej.api.android; +import android.util.SparseArray; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; @@ -22,6 +24,7 @@ import cy.agorise.graphenej.RPC; import cy.agorise.graphenej.Transaction; import cy.agorise.graphenej.UserAccount; import cy.agorise.graphenej.api.ApiAccess; +import cy.agorise.graphenej.api.ApiCallback; import cy.agorise.graphenej.api.ConnectionStatusUpdate; import cy.agorise.graphenej.api.calls.ApiCallable; import cy.agorise.graphenej.api.calls.GetAccountBalances; @@ -117,6 +120,8 @@ public class NetworkService { private CompositeDisposable mCompositeDisposable; + private HashMap mCallbackMap = new HashMap(); + private Gson gson = new GsonBuilder() .registerTypeAdapter(Transaction.class, new Transaction.TransactionDeserializer()) .registerTypeAdapter(TransferOperation.class, new TransferOperation.TransferDeserializer()) @@ -228,6 +233,18 @@ public class NetworkService { return -1; } + public synchronized long sendMessage(ApiCallable apiCallable, int requiredApi, ApiCallback callback){ + long id = this.sendMessage(apiCallable, requiredApi); + if(callback != null){ + if(id != -1){ + mCallbackMap.put(id, callback); + }else{ + callback.onFailure(new Exception("Message could not be sent"), null); + } + } + return id; + } + /** * Method used to inform any external party a clue about the current connectivity status * @return True if the service is currently connected and logged in, false otherwise. @@ -247,6 +264,7 @@ public class NetworkService { nodeLatencyVerifier.stop(); mCompositeDisposable.dispose(); + mCallbackMap.clear(); } /** @@ -303,6 +321,7 @@ public class NetworkService { FullNode fullNode = nodeProvider.getBestNode(); if(fullNode != null){ System.out.println(String.format(Locale.ROOT, "Connected with %d latency results", latencyUpdateCounter)); + mApiIds.clear(); connect(); }else{ Disposable d = Observable.timer(DEFAULT_INITIAL_DELAY, TimeUnit.MILLISECONDS).subscribe(this); @@ -334,6 +353,21 @@ public class NetworkService { public void onComplete() { } }; + /** + * Method used to execute every callback failure method and remove them from the SparseArray. + * + * @param throwable + * @param response + */ + private void resetCallbacks(Throwable throwable, Response response){ + for(ApiCallback callback : mCallbackMap.values()) { + if(callback != null) { + callback.onFailure(throwable, response); + mCallbackMap.remove(callback); + } + } + } + private WebSocketListener mWebSocketListener = new WebSocketListener() { @Override @@ -448,6 +482,7 @@ public class NetworkService { */ private void handleJsonRpcResponse(JsonRpcResponse response, String text){ JsonRpcResponse parsedResponse = null; + Class requestClass = mRequestClassMap.get(response.id); if(requestClass != null){ // Removing the class entry in the map @@ -526,6 +561,17 @@ public class NetworkService { if(parsedResponse == null){ parsedResponse = response; } + + // Executing callback, if present with the parsed response + if(mCallbackMap.containsKey(response.id)){ + ApiCallback callback = mCallbackMap.get(response.id); + if(response.error == null) + callback.onResponse(parsedResponse, text); + else + callback.onFailure(new Exception("Exception while trying to parse node response. Message: " + response.error.message), null); + mCallbackMap.remove(response.id); + } + // Broadcasting the parsed response to all interested listeners RxBus.getBusInstance().send(parsedResponse); } @@ -589,6 +635,7 @@ public class NetworkService { @Override public void onClosed(WebSocket webSocket, int code, String reason) { super.onClosed(webSocket, code, reason); + resetCallbacks(new Exception("Websocket closed. Reason: " + reason), null); if(code == GOING_AWAY_STATUS) handleWebSocketDisconnection(true, false); else @@ -598,6 +645,7 @@ public class NetworkService { @Override public void onFailure(WebSocket webSocket, Throwable t, Response response) { super.onFailure(webSocket, t, response); + resetCallbacks(t, response); System.out.println("onFailure. Exception: "+t.getClass().getName()+", Msg: "+t.getMessage()); // Logging error stack trace for(StackTraceElement element : t.getStackTrace()){