【问题标题】:How to share HttpClient between Multiplatform Ktor and Coil?如何在 Multiplatform Ktor 和 Coil 之间共享 HttpClient?
【发布时间】:2021-10-06 02:07:31
【问题描述】:

我想使用 Coil 图像库从 api 加载图像,使用之前设置的相同 cookie。因此,我想为我的 Ktor 网络调用和使用线圈的图像加载使用相同的 HttpClient。

如何在 Ktor 和 Coil 之间共享同一个 HttpClient?我想,我需要以某种方式调整依赖关系,但我无法理解它。

共享模块中的我的 KtorApiImpl

class KtorApiImpl(log: Kermit) : KtorApi {
val baseUrl = BuildKonfig.baseUrl

// If this is a constructor property, then it gets captured
// inside HttpClient config and freezes this whole class.
@Suppress("CanBePrimaryConstructorProperty")
private val log = log

override val client = HttpClientProvider().getHttpClient().config {
    install(JsonFeature) {
        serializer = KotlinxSerializer()
    }
    install(Logging) {
        logger = object : Logger {
            override fun log(message: String) {
                log.v("Network") { message }
            }
        }

        level = LogLevel.INFO
    }
}

init {
    ensureNeverFrozen()
}

override fun HttpRequestBuilder.apiUrl(path: String) {
    url {
        takeFrom(baseUrl)
        encodedPath = path
    }
}

override fun HttpRequestBuilder.json() {
    contentType(ContentType.Application.Json)
}

}

androidMain 中的实际 HttpClientProvider

var cookieJar: CookieJar = object : CookieJar {
    private val cookieStore: HashMap<String, List<Cookie>> = HashMap()

    override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
        cookieStore[url.host] = cookies
    }

    override fun loadForRequest(url: HttpUrl): List<Cookie> {
        val cookies = cookieStore[url.host]
        return cookies ?: ArrayList()
    }
}


actual class HttpClientProvider actual constructor() {
    actual fun getHttpClient(): HttpClient {
        return HttpClient(OkHttp) {
            engine {
                preconfigured = getOkHttpClient()
            }
        }
    }
}

private fun getOkHttpClient(): OkHttpClient {
    return OkHttpClient.Builder()
        .cookieJar(cookieJar)
        .build()
}

androidApp 中的 ImageLoaderFactory - 如何使用 HttpClient 而不是创建新的?

class CoilImageLoaderFactory(private val context: Context) : ImageLoaderFactory {
    override fun newImageLoader(): ImageLoader {
        return ImageLoader.Builder(context)
            .availableMemoryPercentage(0.25) // Use 25% of the application's available memory.
            .crossfade(true) // Show a short crossfade when loading images from network or disk.
            .componentRegistry {
                add(ByteArrayFetcher())
            }
            .okHttpClient {
                // Create a disk cache with "unlimited" size. Don't do this in production.
                // To create the an optimized Coil disk cache, use CoilUtils.createDefaultCache(context).
                val cacheDirectory = File(context.filesDir, "image_cache").apply { mkdirs() }
                val cache = Cache(cacheDirectory, Long.MAX_VALUE)

                // Lazily create the OkHttpClient that is used for network operations.
                OkHttpClient.Builder()
                    .cache(cache)
                    .build()
            }
            .build()
    }

}

androidApp 中的 Koin 依赖项

@Suppress("unused")
class MainApp : Application() {

    override fun onCreate() {
        super.onCreate()
        initKoin(
        module {
            single<Context> { this@MainApp }
            single<AppInfo> { AndroidAppInfo }
            single { CoilImageLoaderFactory(get<Context>())}
            single<SharedPreferences> {
                get<Context>().getSharedPreferences("MAIN_SETTINGS", Context.MODE_PRIVATE)
            }
            single {
                { Log.i("Startup", "Hello from Android/Kotlin!") }
            }
        }
        )
    }
}

然后是主活动

class MainActivity : AppCompatActivity() { 
    val loaderFactory: CoilImageLoaderFactory by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            CompositionLocalProvider(LocalImageLoader provides loaderFactory.newImageLoader()) {
                MainTheme {
                    ProvideWindowInsets {
                        Surface {
                            MainScreen()
                        }
                    }
                }
            }
        }
    }
}

【问题讨论】:

    标签: kotlin-multiplatform ktor koin kotlin-multiplatform-mobile coil


    【解决方案1】:

    您可以使用ImageLoader.Builder.callFactory{} 提供您自己的Call.Factory 用于网络请求。缺点是您必须将 KtorApiImpl 返回的任何类型映射到 Coil 可以理解的 okttp3.Response

    这是一个示例,描述了如何实现Call.Factory 接口并将其提供给 Coil 的ImageLoader

    ImageLoader.Builder(context)
                .callFactory {
                    Call.Factory {
                        object: Call {
                            private var job: Job? = null
                            override fun clone(): Call {
                                TODO(“Not yet implemented”)
                            }
    
                            override fun request(): Request {
                                return it
                            }
    
                            override fun execute(): Response {
                                return runBlocking {
                                    // Call KTOR client here
                                }
                            }
    
                            override fun enqueue(responseCallback: Callback) {
                                // Use a proper coroutines scope
                                job = GlobalScope.launch {
                                    // Call KTOR client here
                                }
                            }
    
                            override fun cancel() {
                                job?.cancel()
                            }
    
                            override fun isExecuted(): Boolean {
                                return job?.isCompleted ?: false
                            }
    
                            override fun isCanceled(): Boolean {
                                return job?.isCancelled ?: false
                            }
    
                            override fun timeout(): Timeout {
                                // Your Timeout here
                            }
                        }
                    }
                }
    

    【讨论】:

      【解决方案2】:

      我通过 ImageLoader 访问了 OkHttpClient

      class CoilImageLoaderFactory(private val context: Context) : ImageLoaderFactory, KoinComponent {
      val ktorApiImpl: KtorApi by inject()
      
      override fun newImageLoader(): ImageLoader {
          return ImageLoader.Builder(context)
              .componentRegistry {
                  add(ByteArrayFetcher())
              }
              .okHttpClient {
                  val config = ktorApiImpl.client.engine.config as OkHttpConfig
                  config.preconfigured as OkHttpClient
                  
              }
      
      
              .build()
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-06-28
        • 1970-01-01
        • 1970-01-01
        • 2020-04-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多