mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Use Ktor/Ktorfit for API calls (#3122)
This commit is contained in:
parent
d600d182b5
commit
bec5dac9d4
13 changed files with 173 additions and 217 deletions
|
|
@ -22,6 +22,7 @@ plugins {
|
|||
alias(libs.plugins.dokka)
|
||||
alias(libs.plugins.kover)
|
||||
alias(libs.plugins.protobuf)
|
||||
alias(libs.plugins.ktorfit)
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
@ -30,7 +31,7 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.bundles.retrofit)
|
||||
implementation(libs.bundles.ktor)
|
||||
implementation(libs.bundles.coil)
|
||||
"googleImplementation"(libs.bundles.datadog)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.network.di
|
||||
|
||||
import com.geeksville.mesh.network.BuildConfig
|
||||
import com.geeksville.mesh.network.model.NetworkDeviceHardware
|
||||
import com.geeksville.mesh.network.model.NetworkFirmwareReleases
|
||||
import com.geeksville.mesh.network.service.ApiService
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import javax.inject.Singleton
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
class FDroidNetworkModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder()
|
||||
.addInterceptor(
|
||||
interceptor =
|
||||
HttpLoggingInterceptor().apply {
|
||||
if (BuildConfig.DEBUG) {
|
||||
setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
}
|
||||
},
|
||||
)
|
||||
.build()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideApiService(): ApiService = object : ApiService {
|
||||
override suspend fun getDeviceHardware(): List<NetworkDeviceHardware> =
|
||||
throw NotImplementedError("API calls to getDeviceHardware are not supported on Fdroid builds.")
|
||||
|
||||
override suspend fun getFirmwareReleases(): NetworkFirmwareReleases =
|
||||
throw NotImplementedError("API calls to getFirmwareReleases are not supported on Fdroid builds.")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.network.retrofit
|
||||
|
||||
import com.geeksville.mesh.network.model.NetworkDeviceHardware
|
||||
import com.geeksville.mesh.network.model.NetworkFirmwareReleases
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import retrofit2.Response
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
private const val ERROR_NO_OP = 420
|
||||
@Singleton
|
||||
class NoOpApiService@Inject constructor() : ApiService {
|
||||
override suspend fun getDeviceHardware(): Response<List<NetworkDeviceHardware>> {
|
||||
return Response.error(ERROR_NO_OP, "Not Found".toResponseBody(null))
|
||||
}
|
||||
|
||||
override suspend fun getFirmwareReleases(): Response<NetworkFirmwareReleases> {
|
||||
return Response.error(ERROR_NO_OP, "Not Found".toResponseBody(null))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.network.di
|
||||
|
||||
import android.content.Context
|
||||
import coil3.ImageLoader
|
||||
import coil3.disk.DiskCache
|
||||
import coil3.memory.MemoryCache
|
||||
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
|
||||
import coil3.request.crossfade
|
||||
import coil3.svg.SvgDecoder
|
||||
import coil3.util.DebugLogger
|
||||
import coil3.util.Logger
|
||||
import com.datadog.android.okhttp.DatadogEventListener
|
||||
import com.datadog.android.okhttp.DatadogInterceptor
|
||||
import com.geeksville.mesh.network.BuildConfig
|
||||
import com.geeksville.mesh.network.retrofit.ApiService
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
import javax.inject.Singleton
|
||||
|
||||
private const val DISK_CACHE_PERCENT = 0.02
|
||||
private const val MEMORY_CACHE_PERCENT = 0.25
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
class ApiModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOkHttpClient(): OkHttpClient {
|
||||
|
||||
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
||||
if (BuildConfig.DEBUG) {
|
||||
setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
}
|
||||
}
|
||||
val tracedHosts = listOf("meshtastic.org")
|
||||
return OkHttpClient.Builder()
|
||||
.addInterceptor(loggingInterceptor)
|
||||
.addInterceptor(DatadogInterceptor.Builder(tracedHosts).build())
|
||||
.eventListenerFactory(DatadogEventListener.Factory())
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRetrofit(
|
||||
okHttpClient: OkHttpClient
|
||||
): Retrofit {
|
||||
return Retrofit.Builder()
|
||||
.baseUrl("https://api.meshtastic.org/") // Replace with your base URL
|
||||
.addConverterFactory(
|
||||
Json.asConverterFactory(
|
||||
"application/json; charset=UTF8".toMediaType()
|
||||
)
|
||||
)
|
||||
.client(okHttpClient)
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideApiService(retrofit: Retrofit): ApiService {
|
||||
return retrofit.create(ApiService::class.java)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun imageLoader(
|
||||
httpClient: OkHttpClient,
|
||||
@ApplicationContext application: Context,
|
||||
): ImageLoader {
|
||||
val sharedOkHttp = httpClient.newBuilder().build()
|
||||
return ImageLoader.Builder(application)
|
||||
.components {
|
||||
add(
|
||||
OkHttpNetworkFetcherFactory({ sharedOkHttp })
|
||||
)
|
||||
add(SvgDecoder.Factory())
|
||||
}
|
||||
.memoryCache {
|
||||
MemoryCache.Builder()
|
||||
.maxSizePercent(application, MEMORY_CACHE_PERCENT)
|
||||
.build()
|
||||
}
|
||||
.diskCache {
|
||||
DiskCache.Builder()
|
||||
.maxSizePercent(DISK_CACHE_PERCENT)
|
||||
.build()
|
||||
}
|
||||
.logger(if (BuildConfig.DEBUG) DebugLogger(Logger.Level.Verbose) else null)
|
||||
.crossfade(true)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.network.di
|
||||
|
||||
import com.datadog.android.okhttp.DatadogEventListener
|
||||
import com.datadog.android.okhttp.DatadogInterceptor
|
||||
import com.geeksville.mesh.network.BuildConfig
|
||||
import com.geeksville.mesh.network.service.ApiService
|
||||
import com.geeksville.mesh.network.service.createApiService
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import de.jensklingenberg.ktorfit.Ktorfit
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.okhttp.OkHttp
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import javax.inject.Singleton
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
class GoogleNetworkModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder()
|
||||
.addInterceptor(
|
||||
interceptor =
|
||||
HttpLoggingInterceptor().apply {
|
||||
if (BuildConfig.DEBUG) {
|
||||
setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
}
|
||||
},
|
||||
)
|
||||
.addInterceptor(interceptor = DatadogInterceptor.Builder(tracedHosts = listOf("meshtastic.org")).build())
|
||||
.eventListenerFactory(eventListenerFactory = DatadogEventListener.Factory())
|
||||
.build()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideHttpClient(okHttpClient: OkHttpClient): HttpClient = HttpClient(engineFactory = OkHttp) {
|
||||
engine { preconfigured = okHttpClient }
|
||||
|
||||
install(plugin = ContentNegotiation) {
|
||||
json(
|
||||
Json {
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideApiService(httpClient: HttpClient): ApiService {
|
||||
val ktorfit = Ktorfit.Builder().baseUrl(url = "https://api.meshtastic.org/").httpClient(httpClient).build()
|
||||
return ktorfit.createApiService()
|
||||
}
|
||||
}
|
||||
|
|
@ -18,15 +18,12 @@
|
|||
package com.geeksville.mesh.network
|
||||
|
||||
import com.geeksville.mesh.network.model.NetworkDeviceHardware
|
||||
import com.geeksville.mesh.network.retrofit.ApiService
|
||||
import com.geeksville.mesh.network.service.ApiService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class DeviceHardwareRemoteDataSource @Inject constructor(
|
||||
private val apiService: ApiService,
|
||||
) {
|
||||
suspend fun getAllDeviceHardware(): List<NetworkDeviceHardware>? = withContext(Dispatchers.IO) {
|
||||
apiService.getDeviceHardware().body()
|
||||
}
|
||||
class DeviceHardwareRemoteDataSource @Inject constructor(private val apiService: ApiService) {
|
||||
suspend fun getAllDeviceHardware(): List<NetworkDeviceHardware> =
|
||||
withContext(Dispatchers.IO) { apiService.getDeviceHardware() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,15 +18,12 @@
|
|||
package com.geeksville.mesh.network
|
||||
|
||||
import com.geeksville.mesh.network.model.NetworkFirmwareReleases
|
||||
import com.geeksville.mesh.network.retrofit.ApiService
|
||||
import com.geeksville.mesh.network.service.ApiService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class FirmwareReleaseRemoteDataSource @Inject constructor(
|
||||
private val apiService: ApiService,
|
||||
) {
|
||||
suspend fun getFirmwareReleases(): NetworkFirmwareReleases? = withContext(Dispatchers.IO) {
|
||||
apiService.getFirmwareReleases().body()
|
||||
}
|
||||
class FirmwareReleaseRemoteDataSource @Inject constructor(private val apiService: ApiService) {
|
||||
suspend fun getFirmwareReleases(): NetworkFirmwareReleases =
|
||||
withContext(Dispatchers.IO) { apiService.getFirmwareReleases() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@ import coil3.svg.SvgDecoder
|
|||
import coil3.util.DebugLogger
|
||||
import coil3.util.Logger
|
||||
import com.geeksville.mesh.network.BuildConfig
|
||||
import com.geeksville.mesh.network.retrofit.ApiService
|
||||
import com.geeksville.mesh.network.retrofit.NoOpApiService
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -42,40 +40,23 @@ private const val MEMORY_CACHE_PERCENT = 0.25
|
|||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
class ApiModule {
|
||||
class NetworkModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideApiService(): ApiService {
|
||||
return NoOpApiService()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun imageLoader(
|
||||
httpClient: OkHttpClient,
|
||||
@ApplicationContext application: Context,
|
||||
): ImageLoader {
|
||||
val sharedOkHttp = httpClient.newBuilder().build()
|
||||
return ImageLoader.Builder(application)
|
||||
fun provideImageLoader(okHttpClient: OkHttpClient, @ApplicationContext application: Context): ImageLoader {
|
||||
val sharedOkHttp = okHttpClient.newBuilder().build()
|
||||
return ImageLoader.Builder(context = application)
|
||||
.components {
|
||||
add(
|
||||
OkHttpNetworkFetcherFactory({ sharedOkHttp })
|
||||
)
|
||||
add(OkHttpNetworkFetcherFactory(callFactory = { sharedOkHttp }))
|
||||
add(SvgDecoder.Factory())
|
||||
}
|
||||
.memoryCache {
|
||||
MemoryCache.Builder()
|
||||
.maxSizePercent(application, MEMORY_CACHE_PERCENT)
|
||||
.build()
|
||||
MemoryCache.Builder().maxSizePercent(context = application, percent = MEMORY_CACHE_PERCENT).build()
|
||||
}
|
||||
.diskCache {
|
||||
DiskCache.Builder()
|
||||
.maxSizePercent(DISK_CACHE_PERCENT)
|
||||
.build()
|
||||
}
|
||||
.logger(if (BuildConfig.DEBUG) DebugLogger(Logger.Level.Verbose) else null)
|
||||
.crossfade(true)
|
||||
.diskCache { DiskCache.Builder().maxSizePercent(percent = DISK_CACHE_PERCENT).build() }
|
||||
.logger(logger = if (BuildConfig.DEBUG) DebugLogger(minLevel = Logger.Level.Verbose) else null)
|
||||
.crossfade(enable = true)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
|
@ -15,17 +15,16 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.network.retrofit
|
||||
package com.geeksville.mesh.network.service
|
||||
|
||||
import com.geeksville.mesh.network.model.NetworkDeviceHardware
|
||||
import com.geeksville.mesh.network.model.NetworkFirmwareReleases
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.GET
|
||||
import de.jensklingenberg.ktorfit.http.GET
|
||||
|
||||
interface ApiService {
|
||||
@GET("resource/deviceHardware")
|
||||
suspend fun getDeviceHardware(): Response<List<NetworkDeviceHardware>>
|
||||
suspend fun getDeviceHardware(): List<NetworkDeviceHardware>
|
||||
|
||||
@GET("/github/firmware/list")
|
||||
suspend fun getFirmwareReleases(): Response<NetworkFirmwareReleases>
|
||||
@GET("github/firmware/list")
|
||||
suspend fun getFirmwareReleases(): NetworkFirmwareReleases
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue