diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..8c311adc --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: koush +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.gitignore b/.gitignore index 672a70a9..7381bc65 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ gradlew gradlew.bat ion.iml +*.iml diff --git a/README.md b/README.md index e1bdaf3c..aace5ca0 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ ![](ion-sample/ion-sample.png) #### Download - * [Jar](https://github.com/koush/ion#get-ion) * [Maven](https://github.com/koush/ion#get-ion) * [Git](https://github.com/koush/ion#get-ion) #### Features + * [Kotlin coroutine/suspend support](https://github.com/koush/AndroidAsync/blob/master/AndroidAsync-Kotlin/README.md) * Asynchronously download: * [Images](https://github.com/koush/ion#load-an-image-into-an-imageview) into ImageViews or Bitmaps (animated GIFs supported too) * [JSON](https://github.com/koush/ion#get-json) (via [Gson](https://code.google.com/p/google-gson/)) @@ -48,12 +48,12 @@ The included documented [ion-sample](https://github.com/koush/ion/tree/master/io * Populate a ListView Adapter and fetch more data as you scroll to the end * Put images from a URLs into ImageViews (twitter profile pictures) * File Download with [Progress Bar Sample](https://github.com/koush/ion/blob/master/ion-sample/src/com/koushikdutta/ion/sample/ProgressBarDownload.java) - * Get JSON and show images with the [Google Image Search Sample](https://github.com/koush/ion/blob/master/ion-sample/src/com/koushikdutta/ion/sample/GoogleImageSearch.java) + * Get JSON and show images with the [Image Search Sample](https://github.com/koush/ion/blob/master/ion-sample/src/com/koushikdutta/ion/sample/ImageSearch.java) #### More Examples Looking for more? Check out the examples below that demonstrate some other common scenarios. You can also take a look -at 30+ ion unit tests in the [ion-test](https://github.com/koush/ion/tree/master/ion-test/src/com/koushikdutta/ion/test). +at 30+ ion unit tests in the [ion-test](https://github.com/koush/ion/tree/master/ion/test/src/com/koushikdutta/ion/test). #### Get JSON @@ -105,7 +105,7 @@ Ion.with(getContext()) .load("https://koush.clockworkmod.com/test/echo") .uploadProgressBar(uploadProgressBar) .setMultipartParameter("goop", "noop") -.setMultipartFile("filename.zip", new File("/sdcard/filename.zip")) +.setMultipartFile("archive", "application/zip", new File("/sdcard/filename.zip")) .asJsonObject() .setCallback(...) ``` @@ -121,7 +121,7 @@ Ion.with(context) .progressDialog(progressDialog) // can also use a custom callback .progress(new ProgressCallback() {@Override - public void onProgress(int downloaded, int total) { + public void onProgress(long downloaded, long total) { System.out.println("" + downloaded + " / " + total); } }) @@ -170,7 +170,7 @@ Ion.with(imageView) The Ion Image load API has the following features: * Disk and memory caching - * Bitmaps are held via weak references so memory is managed very effeciently + * Bitmaps are held via weak references so memory is managed very efficiently * ListView Adapter recycling support * Bitmap transformations via the .transform(Transform) * Animate loading and loaded ImageView states @@ -290,8 +290,8 @@ Future json2 = Ion.with(activity, "http://example.com/test2.json").a // later... in activity.onStop @Override protected void onStop() { - super.onStop(); Ion.getDefault(activity).cancelAll(activity); + super.onStop(); } ``` @@ -373,23 +373,19 @@ Ion.with(getContext()) #### Get Ion -##### Jars - * [androidasync.jar](https://search.maven.org/remote_content?g=com.koushikdutta.async&a=androidasync&v=LATEST) (dependency) - * [ion.jar](https://search.maven.org/remote_content?g=com.koushikdutta.ion&a=ion&v=LATEST) - ##### Maven ```xml com.koushikdutta.ion ion - 2, + (insert latest version) ``` ##### Gradle ```groovy dependencies { - compile 'com.koushikdutta.ion:ion:2.+' + compile 'com.koushikdutta.ion:ion:(insert latest version)' } ```` @@ -424,3 +420,6 @@ There's hundreds of apps using ion. Feel free to contact me or submit a pull req * [PictureCast](https://play.google.com/store/apps/details?id=com.unstableapps.picturecast.app) * [Eventius](https://play.google.com/store/apps/details?id=com.eventius.android) * [Plume](https://play.google.com/store/apps/details?id=com.levelup.touiteur) +* [GameRaven](https://play.google.com/store/apps/details?id=com.ioabsoftware.gameraven) +* [See You There](https://play.google.com/store/apps/details?id=com.maps.wearat&hl=en) +* [Doogles](https://play.google.com/store/apps/details?id=io.dooglesapp) diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..b4144f2a --- /dev/null +++ b/build.gradle @@ -0,0 +1,22 @@ +buildscript { + repositories { + jcenter() + google() + mavenCentral() + } + dependencies { + classpath project.hasProperty('global_gradleAndroidPlugin') ? global_gradleAndroidPlugin : 'com.android.tools.build:gradle:3.2.0' + } +} + +subprojects { + repositories { + jcenter() + google() + mavenCentral() + } + + ext.global_compileSdkVersion = project.hasProperty('global_compileSdkVersion') ? global_compileSdkVersion : 28 + ext.global_buildToolsVersion = project.hasProperty('global_buildToolsVersion') ? global_buildToolsVersion : '28.0.3' + ext.global_gradleAndroidPlugin = project.hasProperty('global_gradleAndroidPlugin') ? global_gradleAndroidPlugin : 'com.android.tools.build:gradle:3.2.0' +} diff --git a/ion-kotlin/AndroidManifest.xml b/ion-kotlin/AndroidManifest.xml new file mode 100644 index 00000000..48a0446f --- /dev/null +++ b/ion-kotlin/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/ion-kotlin/README.md b/ion-kotlin/README.md new file mode 100644 index 00000000..7dcf0fe8 --- /dev/null +++ b/ion-kotlin/README.md @@ -0,0 +1,109 @@ +# Ion Kotlin Extensions - async/await + +async/await allows you to write code that looks like synchronous code, but is actually run asynchronously via coroutines. + +For example, if you wanted to download a list of files with async/await in Ion: + +```kotlin +fun getFiles(files: Array) = async { + for (file in files) { + Ion.with(context) + .load(file) + .asString() + .await() + } +} +``` +This may look like synchronous code, but it is not. The return type of getFiles is a actually [Future](https://github.com/koush/ion#futures). The operation happens asynchronously, and only when all the files are finished downloading, will the Future's callback be called. + +The code in an async block is a [suspend fun, aka a coroutine](https://kotlinlang.org/docs/reference/coroutines.html). +```kotlin +async { + // the code in here is a suspend fun, a coroutine. + // execution can be suspended and resumed. +} +``` + +Inside an async block, you can use await on Futures to wait for their results. + +```kotlin +fun getFiles(files: File) = async { + System.out.println("Bob") + + val string = Ion.with(context) + .load(file) + .asString() + .await() + + System.out.println("Chuck") +} + +System.out.println("Alice") +getFiles(file) +System.out.println("David") +``` + +async blocks are run asynchronously, but look synchronous. The output from this code would be: + +``` +Alice +Bob +David +Chuck +``` + +Execution was paused at the await() call, and resumed after the file was downloaded. + +Await can also be used to switch thread affinities: + +```kotlin +async { + looper.await() + System.out.println("I'm running on this Looper thread.") + + handler.await() + System.out.println("I'm running on this handler's Looper thread.") + + executor.await() + System.out.println("I'm running on this Executor's thread pool.") + + asyncServer.await() + System.out.println("I'm running on this AsyncServer's reactor thread.") +} +``` + +### Getting results from async functions + +async lets you easily write functions that return Futures. + +```kotlin +fun myStringFunction(url: String) = async { + try { + return@async Ion.with(context) + .load(url) + .asString() + .await() + } + catch (e: Exception) { + return@async "Failed to load" + } +} +``` + +myStringFunction returns a Future. Can attach setCallback to get the result, or use it in other async code blocks + +Or for brevity: + +```kotlin +fun myStringFunction(url: String) = async { + try { + Ion.with(context) + .load(url) + .asString() + .await() + } + catch (e: Exception) { + "Failed to load" + } +} +``` diff --git a/ion-kotlin/build.gradle b/ion-kotlin/build.gradle new file mode 100644 index 00000000..a323eb02 --- /dev/null +++ b/ion-kotlin/build.gradle @@ -0,0 +1,63 @@ +buildscript { + repositories { + google() + mavenCentral() + jcenter() + } + ext.kotlin_version = '1.3.61' + dependencies { + classpath 'com.android.tools.build:gradle:3.6.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +dependencies { + api project(':ion:ion') + api project(':AndroidAsync:AndroidAsync-Kotlin') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61" + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2' + + testImplementation 'junit:junit:4.12' + testImplementation 'org.jetbrains.kotlin:kotlin-test-junit:1.3.61' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' +} + +android { + kotlinOptions { + apiVersion = "1.3" + languageVersion = "1.3" + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + res.srcDirs = ['res/'] + java.srcDirs = ['src/'] + assets.srcDirs = ['assets/'] + } + androidTest.java.srcDirs=['test/src/'] + androidTest.res.srcDirs=['test/res/'] + androidTest.assets.srcDirs=['test/assets/'] + } + + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + + defaultConfig { + targetSdkVersion 26 + minSdkVersion 14 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + + compileSdkVersion project.hasProperty('global_compileSdkVersion') ? global_compileSdkVersion : 26 + buildToolsVersion project.hasProperty('global_buildToolsVersion') ? global_buildToolsVersion : '26.0.2' +} diff --git a/ion-kotlin/test/src/com/koushikdutta/ion/kotlin/test/ExampleInstrumentedTest.kt b/ion-kotlin/test/src/com/koushikdutta/ion/kotlin/test/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..d3a9eea8 --- /dev/null +++ b/ion-kotlin/test/src/com/koushikdutta/ion/kotlin/test/ExampleInstrumentedTest.kt @@ -0,0 +1,38 @@ +package com.koushikdutta.ion.kotlin.test + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.runner.AndroidJUnit4 +import com.koushikdutta.async.kotlin.await +import com.koushikdutta.ion.Ion +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.async +import org.junit.Test +import org.junit.runner.RunWith +import java.util.concurrent.Semaphore +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest : CoroutineScope { + override val coroutineContext: CoroutineContext + get() = EmptyCoroutineContext + + @Test + fun testIon() { + val semaphore = Semaphore(0) + async { + val ret = Ion.with(InstrumentationRegistry.getInstrumentation().targetContext) + .load("https://google.com/robots.txt") + .asString() + .await() + println(ret) + semaphore.release() + } + semaphore.acquire() + } +} diff --git a/ion-kotlin/test/src/com/koushikdutta/ion/kotlin/test/KotlinTests.kt b/ion-kotlin/test/src/com/koushikdutta/ion/kotlin/test/KotlinTests.kt new file mode 100644 index 00000000..baeaa336 --- /dev/null +++ b/ion-kotlin/test/src/com/koushikdutta/ion/kotlin/test/KotlinTests.kt @@ -0,0 +1,16 @@ +package com.koushikdutta.ion.kotlin.test + +import kotlinx.coroutines.CoroutineScope +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + + +/** + * Created by koush on 7/2/17. + */ + +class KotlinTests(override val coroutineContext: CoroutineContext = EmptyCoroutineContext) : CoroutineScope { + suspend fun testIon() { + + } +} \ No newline at end of file diff --git a/ion-sample/AndroidManifest.xml b/ion-sample/AndroidManifest.xml index 734050f9..fad06d24 100644 --- a/ion-sample/AndroidManifest.xml +++ b/ion-sample/AndroidManifest.xml @@ -4,10 +4,6 @@ android:versionCode="1" android:versionName="1.0"> - - @@ -18,8 +14,11 @@ android:icon="@drawable/ic_launcher" android:theme="@style/Theme"> + android:name=".Twitter" + android:label="Twitter Client Sample"/> + @@ -30,8 +29,8 @@ android:name=".ProgressBarUpload" android:label="Progress Bar Upload"/> + android:name=".ImageSearch" + android:label="Image Search"/> @@ -39,8 +38,14 @@ android:name=".ImageViewSample" android:label="ImageView Sample"/> + android:name=".DeepZoomSample" + android:label="DeepZoom Sample"/> + + + + + + + + + + + \ No newline at end of file diff --git a/ion-sample/res/layout/samples.xml b/ion-sample/res/layout/samples.xml index 5693ae9c..f4f0e6e8 100644 --- a/ion-sample/res/layout/samples.xml +++ b/ion-sample/res/layout/samples.xml @@ -1,72 +1,97 @@ + android:orientation="horizontal" + android:gravity="center" + android:layout_width="match_parent" + android:layout_height="match_parent"> -