Skip to content

Commit

Permalink
创建方式改变为FlowCallAdapterFactory.create()和FlowCallAdapterFactory.createA…
Browse files Browse the repository at this point in the history
…sync(),现在支持同步请求。
  • Loading branch information
chenxyu committed Jul 21, 2021
1 parent b93e2cb commit 3263f7b
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 87 deletions.
27 changes: 12 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
# 依赖版本
| Dependency | Version |
|--|--|
| kotlin | 1.4.32 |
| kotlinx-coroutines-android | 1.4.3 |
| kotlin | 1.5.21 |
| kotlinx-coroutines-android | 1.5.1 |
| retrofit | 2.9.0 |

# Gradle 依赖
Expand All @@ -26,7 +26,7 @@ allprojects {

```kotlin
dependencies {
implementation 'com.github.chenxyu:retrofit-adapter-flow:1.1.4'
implementation 'com.github.chenxyu:retrofit-adapter-flow:1.2.0'
}
```

Expand All @@ -38,21 +38,18 @@ fun login(@Body any: Any): Flow<User>

Retrofit.Builder()
...
.addCallAdapterFactory(FlowCallAdapterFactory())
.addCallAdapterFactory(FlowCallAdapterFactory.create())
.build()

lifecycleScope.launch(Dispatchers.Main) {
var resultFlow: Flow<String>? = null
withContext(Dispatchers.IO) {
resultFlow = githubService.search("kotlinx.coroutines")
}
resultFlow?.catch { e ->
// 异常处理
if (e is CancellationException) {
Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_LONG).show()
}
}?.collect {
textView.text = it
githubService.search("kotlinx.coroutines")
.flowOn(Dispatchers.IO)
.catch { e ->
if (e is CancellationException) {
Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_LONG).show()
}
}.collect {
textView.text = it
}
}
```
6 changes: 3 additions & 3 deletions adapter-flow/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ android {
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
versionCode 11
versionName "1.1.4"
versionCode 12
versionName "1.2.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
Expand All @@ -28,7 +28,7 @@ android {

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinx_coroutines_version"
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.chenxyu.retrofit.adapter

import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

/**
* @Author: ChenXingYu
* @CreateDate: 2021/7/21 1:46
* @Description:
* @Version: 1.0
*/
@ExperimentalCoroutinesApi
internal fun <R> ProducerScope<R>.callEnqueueFlow(call: Call<R>) {
call.enqueue(object : Callback<R> {
override fun onResponse(call: Call<R>, response: Response<R>) {
processing(response)
}

override fun onFailure(call: Call<R>, throwable: Throwable) {
cancel(CancellationException(throwable.localizedMessage, throwable))
}
})
}

@ExperimentalCoroutinesApi
internal fun <R> ProducerScope<R>.callExecuteFlow(call: Call<R>) {
try {
processing(call.execute())
} catch (throwable: Throwable) {
cancel(CancellationException(throwable.localizedMessage, throwable))
}
}

@ExperimentalCoroutinesApi
internal fun <R> ProducerScope<R>.processing(response: Response<R>) {
if (response.isSuccessful) {
val body = response.body()
if (body == null || response.code() == 204) {
cancel(CancellationException("HTTP status code: ${response.code()}"))
} else {
trySendBlocking(body)
.onSuccess {
close()
}
.onClosed { throwable ->
cancel(
CancellationException(
throwable?.localizedMessage,
throwable
)
)
}
.onFailure { throwable ->
cancel(
CancellationException(
throwable?.localizedMessage,
throwable
)
)
}
}
} else {
val msg = response.errorBody()?.string()
cancel(
CancellationException(
if (msg.isNullOrEmpty()) {
response.message()
} else {
msg
} ?: "unknown error"
)
)
}
}
Original file line number Diff line number Diff line change
@@ -1,68 +1,38 @@
package com.chenxyu.retrofit.adapter

import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.sendBlocking
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import retrofit2.Call
import retrofit2.CallAdapter
import retrofit2.Callback
import retrofit2.Response
import java.lang.reflect.Type
import java.util.concurrent.atomic.AtomicBoolean

/**
* @Author ChenXingYu
* @Date 2020/4/9-15:21
*/
internal class FlowCallAdapter<R>(private val responseType: Type) :
CallAdapter<R, Flow<R?>> {
internal class FlowCallAdapter<R>(
private val responseType: Type,
private val isAsync: Boolean
) : CallAdapter<R, Flow<R?>> {

override fun responseType() = responseType

@ExperimentalCoroutinesApi
override fun adapt(call: Call<R>): Flow<R?> {
return callFlow(call, isAsync)
}

@ExperimentalCoroutinesApi
private fun <R> callFlow(call: Call<R>, isAsync: Boolean): Flow<R> {
val started = AtomicBoolean(false)
return callbackFlow {
val started = AtomicBoolean(false)
if (started.compareAndSet(false, true)) {
call.enqueue(object : Callback<R> {
override fun onResponse(call: Call<R>, response: Response<R>) {
if (response.isSuccessful) {
val body = response.body()
if (body == null || response.code() == 204) {
cancel(CancellationException("HTTP status code: ${response.code()}"))
} else {
try {
sendBlocking(body)
close()
} catch (e: Exception) {
cancel(CancellationException(e.localizedMessage, e))
}
}
} else {
cancel(CancellationException(errorMsg(response) ?: "unknown error"))
}
}

override fun onFailure(call: Call<R>, throwable: Throwable) {
cancel(CancellationException(throwable.localizedMessage, throwable))
}
})
if (isAsync) callEnqueueFlow(call) else callExecuteFlow(call)
awaitClose { call.cancel() }
}
awaitClose { call.cancel() }
}
}

private fun errorMsg(response: Response<R>): String? {
val msg = response.errorBody()?.string()
return if (msg.isNullOrEmpty()) {
response.message()
} else {
msg
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,29 @@ import java.lang.reflect.Type
* @Author ChenXingYu
* @Date 2020/4/9-15:19
*/
class FlowCallAdapterFactory : CallAdapter.Factory() {
class FlowCallAdapterFactory private constructor(private var isAsync: Boolean) :
CallAdapter.Factory() {
companion object {
/**
* 同步
*/
fun create(): FlowCallAdapterFactory = FlowCallAdapterFactory(false)

/**
* 异步
*/
fun createAsync(): FlowCallAdapterFactory = FlowCallAdapterFactory(true)
}

override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
if (getRawType(returnType) != Flow::class.java) {
return null
}
val observableType = getParameterUpperBound(0, returnType as ParameterizedType)
return FlowCallAdapter<Any>(observableType)
return FlowCallAdapter<Any>(observableType, isAsync)
}
}
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ android {

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.core:core-ktx:1.3.2'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinx_coroutines_version"
Expand Down
25 changes: 10 additions & 15 deletions app/src/main/java/com/chenxyu/example/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@ import androidx.lifecycle.lifecycleScope
import com.chenxyu.retrofit.adapter.FlowCallAdapterFactory
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import retrofit2.Retrofit
import retrofit2.converter.scalars.ScalarsConverterFactory
import retrofit2.http.GET
Expand All @@ -23,7 +20,7 @@ class MainActivity : AppCompatActivity() {
Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(ScalarsConverterFactory.create())
.addCallAdapterFactory(FlowCallAdapterFactory())
.addCallAdapterFactory(FlowCallAdapterFactory.create())
.build()
.create(GithubService::class.java)
}
Expand All @@ -33,17 +30,15 @@ class MainActivity : AppCompatActivity() {
setContentView(R.layout.activity_main)
val textView = findViewById<TextView>(R.id.text_view)
lifecycleScope.launch(Dispatchers.Main) {
var resultFlow: Flow<String>? = null
withContext(Dispatchers.IO) {
resultFlow = githubService.search("kotlinx.coroutines")
}
resultFlow?.catch { e ->
if (e is CancellationException) {
Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_LONG).show()
githubService.search("kotlinx.coroutines")
.flowOn(Dispatchers.IO)
.catch { e ->
if (e is CancellationException) {
Toast.makeText(this@MainActivity, e.message, Toast.LENGTH_LONG).show()
}
}.collect {
textView.text = it
}
}?.collect {
textView.text = it
}
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
ext.kotlin_version = '1.4.32'
ext.kotlinx_coroutines_version = '1.4.3'
ext.kotlin_version = '1.5.21'
ext.kotlinx_coroutines_version = '1.5.1'
repositories {
google()
jcenter()
mavenCentral()
maven { url "https://jitpack.io" }
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.2'
classpath 'com.android.tools.build:gradle:4.2.2'
classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
Expand All @@ -19,7 +20,6 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven { url "https://jitpack.io" }
}
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip

0 comments on commit 3263f7b

Please sign in to comment.