knowm / XChange

XChange is a Java library providing a streamlined API for interacting with 60+ Bitcoin and Altcoin exchanges providing a consistent interface for trading and accessing market data.
http://knowm.org/open-source/xchange/
MIT License
3.85k stars 1.94k forks source link

Android app? #4216

Open hovi opened 3 years ago

hovi commented 3 years ago

I am just wondering, why is there no android app using xchange on google play? Are there any legal or technical reasons why I cannot do that? I'd like to use an app with this simple interface even if I have to make one, but in that case I might as well share it with the world :-)

cymp commented 3 years ago

There is no reason you couldn't use XChange for an Android app. In fact, there is even a discussion somewhere here about migrating to java version>8, but people raised concerns about Android compatibility. If you want to develop an Android app and bring your experience here, it would be more than welcome.

hovi commented 3 years ago

I see, I just read #2945 Thank you, I will bring any useful feedback.

bschnider commented 2 years ago

I do not know if it is the right place to post my question regarding an error I have been facing when trying to simply extract the ticker info from Kraken in my Android application. I am quite new in the Android world and might also have incorrectly imported the library but still thought it could be worth posting my issue here, as I have read that some incompatibilities have been faced with Java 11 (my application is based on a JDK 8).

My import: build.gradle (:app):

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.example.trackmycrypto"
        minSdkVersion 19
        multiDexEnabled true
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation 'androidx.multidex:multidex:2.0.1'
    implementation 'org.knowm.xchange:xchange-core:5.0.13'
    implementation 'org.knowm.xchange:xchange-kraken:5.0.13'
}

_build.gradle (APPNAME):

allprojects {
    repositories {
        google()
        jcenter()
        mavenCentral()
        maven { url 'https://jitpack.io' }
    }
}

I have read your explanations regarding the dependencies to add in the pom.xml file, but I do not have such file in my application's project and usually, the third party libraries are directly imported when synchronizing the dependencies of the gradle files (which I did). Could it be that the import of the library was incomplete or should be performed manually ?

My simple test code:

ExchangeSpecification exSpec = new KrakenExchange().getDefaultExchangeSpecification();
Exchange kraken = ExchangeFactory.INSTANCE.createExchange(exSpec);  //!\\ RAISES THE ERROR
MarketDataService marketDataService = kraken.getMarketDataService();
Ticker ticker = null;
try {
    ticker = marketDataService.getTicker(CurrencyPair.BTC_USD);  // I know this method is deprecated, but this was just a test
} catch (IOException e) {
    e.printStackTrace();
}
Log.d("TEST1", ticker.toString());

The error:

2022-03-31 05:30:00.028 16765-16765/com.example.trackmycrypto E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.trackmycrypto, PID: 16765
    java.lang.NoClassDefFoundError: Failed resolution of: Ljava/time/Duration;
        at org.knowm.xchange.client.ResilienceRegistries.<clinit>(ResilienceRegistries.java:23)
        at org.knowm.xchange.client.ExchangeRestProxyBuilder.build(ExchangeRestProxyBuilder.java:76)
        at org.knowm.xchange.kraken.service.KrakenBaseService.<init>(KrakenBaseService.java:51)
        at org.knowm.xchange.kraken.service.KrakenMarketDataServiceRaw.<init>(KrakenMarketDataServiceRaw.java:31)
        at org.knowm.xchange.kraken.service.KrakenMarketDataService.<init>(KrakenMarketDataService.java:29)
        at org.knowm.xchange.kraken.KrakenExchange.initServices(KrakenExchange.java:19)
        at org.knowm.xchange.BaseExchange.applySpecification(BaseExchange.java:108)
        at org.knowm.xchange.ExchangeFactory.createExchange(ExchangeFactory.java:130)
        at com.example.trackmycrypto.SynchronizeAdapter$ViewHolder3$1.onClick(SynchronizeAdapter.java:192)
        at android.view.View.performClick(View.java:6213)
        at android.widget.TextView.performClick(TextView.java:11074)
        at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119)
        at android.view.View$PerformClick.run(View.java:23645)
        at android.os.Handler.handleCallback(Handler.java:751)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6692)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1468)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1358)
     Caused by: java.lang.ClassNotFoundException: Didn't find class "java.time.Duration" on path: DexPathList[[zip file "/data/app/com.example.trackmycrypto-1/base.apk"],nativeLibraryDirectories=[/data/app/com.example.trackmycrypto-1/lib/arm64, /system/lib64, /vendor/lib64]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:380)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        at org.knowm.xchange.client.ResilienceRegistries.<clinit>(ResilienceRegistries.java:23) 
        at org.knowm.xchange.client.ExchangeRestProxyBuilder.build(ExchangeRestProxyBuilder.java:76) 
        at org.knowm.xchange.kraken.service.KrakenBaseService.<init>(KrakenBaseService.java:51) 
        at org.knowm.xchange.kraken.service.KrakenMarketDataServiceRaw.<init>(KrakenMarketDataServiceRaw.java:31) 
        at org.knowm.xchange.kraken.service.KrakenMarketDataService.<init>(KrakenMarketDataService.java:29) 
        at org.knowm.xchange.kraken.KrakenExchange.initServices(KrakenExchange.java:19) 
        at org.knowm.xchange.BaseExchange.applySpecification(BaseExchange.java:108) 
        at org.knowm.xchange.ExchangeFactory.createExchange(ExchangeFactory.java:130) 
        at com.example.trackmycrypto.SynchronizeAdapter$ViewHolder3$1.onClick(SynchronizeAdapter.java:192) 
        at android.view.View.performClick(View.java:6213) 
        at android.widget.TextView.performClick(TextView.java:11074) 
        at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119) 
        at android.view.View$PerformClick.run(View.java:23645) 
        at android.os.Handler.handleCallback(Handler.java:751) 
        at android.os.Handler.dispatchMessage(Handler.java:95) 
        at android.os.Looper.loop(Looper.java:154) 
        at android.app.ActivityThread.main(ActivityThread.java:6692) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1468) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1358)

The error seems to be caused by the use of Duration with Android API levels < 26 and has been previously documented for other projects. I have tested some methods locally, and most specifically, the methods ofMillis(), ofSeconds() and ofMinutes() are causing the error. According to the other project I have just linked, replacing their use with simple long / integer multications should do the job (see this example).

Is there anyway Duration could be replaced from XChange ? If not, would you have an alternative solution ?

Please feel free to let me know if a new post should be created for my issue, if you would rather want me to post it on stackoverflow or would simply want me to delete it if it is a misunderstanding from my side.

hovi commented 2 years ago

I haven't been playing with xchange and android for a while, but I see minsdk 23 and that is also API level of the device I was using for testing. It was working fine for me. I believe you are just missing desugaring, that deals with issues like this.

bschnider commented 2 years ago

Thank you for mentioning desugaring. I did not know such library existed and it indeed solved my problem. I had to add the following two lines in my build.gradle app's module:

dependencies: coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'

compileOptions: coreLibraryDesugaringEnabled true

This section helped me properly import desugaring and lists all the dependencies and requirements.

bschnider commented 2 years ago

Trying to create my Kraken instance of the Exchange object with a user name, API key and secret key, I got stuck with another problem apparently linked to the use of a java library which is not supported by Android API levels < 26.

My code:

ExchangeSpecification exSpec = new KrakenExchange().getDefaultExchangeSpecification();
// Define API key
exSpec.setUserName("MY_ACCOUNT");
exSpec.setApiKey("MY_API_KEY");
exSpec.setSecretKey("MY_SECRET_KEY");
Exchange kraken = ExchangeFactory.INSTANCE.createExchange(exSpec);

The returned error:

2022-03-31 22:58:39.610 28595-29066/com.example.trackmycrypto E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #3
    Process: com.example.trackmycrypto, PID: 28595
    java.lang.RuntimeException: An error occurred while executing doInBackground()
        at android.os.AsyncTask$3.done(AsyncTask.java:318)
        at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354)
        at java.util.concurrent.FutureTask.setException(FutureTask.java:223)
        at java.util.concurrent.FutureTask.run(FutureTask.java:242)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
        at java.lang.Thread.run(Thread.java:762)
     Caused by: java.lang.NoClassDefFoundError: Failed resolution of: Ljava/util/Base64;
        at org.knowm.xchange.kraken.service.KrakenDigest.createInstance(KrakenDigest.java:29)
        at org.knowm.xchange.kraken.service.KrakenBaseService.<init>(KrakenBaseService.java:53)
        at org.knowm.xchange.kraken.service.KrakenMarketDataServiceRaw.<init>(KrakenMarketDataServiceRaw.java:31)
        at org.knowm.xchange.kraken.service.KrakenMarketDataService.<init>(KrakenMarketDataService.java:29)
        at org.knowm.xchange.kraken.KrakenExchange.initServices(KrakenExchange.java:19)
        at org.knowm.xchange.BaseExchange.applySpecification(BaseExchange.java:108)
        at org.knowm.xchange.ExchangeFactory.createExchange(ExchangeFactory.java:130)
        at com.example.trackmycrypto.SynchronizeAdapter$ProgressTask.doInBackground(SynchronizeAdapter.java:296)
        at com.example.trackmycrypto.SynchronizeAdapter$ProgressTask.doInBackground(SynchronizeAdapter.java:276)
        at android.os.AsyncTask$2.call(AsyncTask.java:304)
        at java.util.concurrent.FutureTask.run(FutureTask.java:237)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243) 
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) 
        at java.lang.Thread.run(Thread.java:762) 
     Caused by: java.lang.ClassNotFoundException: Didn't find class "java.util.Base64" on path: DexPathList[[zip file "/data/app/com.example.trackmycrypto-1/base.apk"],nativeLibraryDirectories=[/data/app/com.example.trackmycrypto-1/lib/arm64, /system/lib64, /vendor/lib64]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:380)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        at org.knowm.xchange.kraken.service.KrakenDigest.createInstance(KrakenDigest.java:29) 
        at org.knowm.xchange.kraken.service.KrakenBaseService.<init>(KrakenBaseService.java:53) 
        at org.knowm.xchange.kraken.service.KrakenMarketDataServiceRaw.<init>(KrakenMarketDataServiceRaw.java:31) 
        at org.knowm.xchange.kraken.service.KrakenMarketDataService.<init>(KrakenMarketDataService.java:29) 
        at org.knowm.xchange.kraken.KrakenExchange.initServices(KrakenExchange.java:19) 
        at org.knowm.xchange.BaseExchange.applySpecification(BaseExchange.java:108) 
        at org.knowm.xchange.ExchangeFactory.createExchange(ExchangeFactory.java:130) 
        at com.example.trackmycrypto.SynchronizeAdapter$ProgressTask.doInBackground(SynchronizeAdapter.java:296) 
        at com.example.trackmycrypto.SynchronizeAdapter$ProgressTask.doInBackground(SynchronizeAdapter.java:276) 
        at android.os.AsyncTask$2.call(AsyncTask.java:304) 
        at java.util.concurrent.FutureTask.run(FutureTask.java:237) 
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243) 
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) 
        at java.lang.Thread.run(Thread.java:762) 
2022-03-31 22:58:39.650 28595-28595/com.example.trackmycrypto D/TextView: setTypeface with style : 0
2022-03-31 22:58:39.657 28595-28595/com.example.trackmycrypto D/TextView: setTypeface with style : 0
2022-03-31 22:58:39.664 28595-28595/com.example.trackmycrypto D/TextView: setTypeface with style : 0
2022-03-31 22:58:39.791 28595-28595/com.example.trackmycrypto D/TextView: setTypeface with style : 0
2022-03-31 22:58:39.795 28595-28595/com.example.trackmycrypto D/TextView: setTypeface with style : 0
2022-03-31 22:58:39.802 28595-28595/com.example.trackmycrypto D/TextView: setTypeface with style : 0
2022-03-31 22:58:39.828 28595-28595/com.example.trackmycrypto D/TextView: setTypeface with style : 0
2022-03-31 22:58:39.834 28595-28595/com.example.trackmycrypto D/TextView: setTypeface with style : 0
2022-03-31 22:58:39.845 28595-28595/com.example.trackmycrypto D/TextView: setTypeface with style : 0

It appears that the library java.util.Base64 is not supported by Android API levels < 26. Apparently, one should use the library android.util.Base64 instead. An alternative solution would be to use another Apache library which is compatible for both Java and Android.

Since XChange has not been developed specifically for Android, I suspect I might face many similar issues. The easiest solution would be to directly modify the unsupported libraries, but I currently cannot do so, as I have imported XChange directly from Maven. Have you faced similar issues in the past ? Would you suggest to clone XChange instead and to import it as a module to be able to directly replace the unsupported library manually (this solution would require more work to update the library) ? Or am I still missing some essential libraries as for desugaring ?

bschnider commented 2 years ago

The only solution I found so far is to use a version of XChange prior to the use of java.util.Base64. I believe the latest version without this dependency would be 4.3.4 . Using this version of the core and Kraken libraries worked on my computer and on my phone.

hovi commented 2 years ago

From my experience I must say the best is to setup whole xchange project, build the latest snapshot on your local and set it as a dependency. Maven/gradle make it very easy to setup and then you can experiment with code changes within xchange itself.

It was just way too often when I had to modify xchange code to get it working on android. You can also then easily submit pull request when it makes sense and to make the whole library more compatible with android.