diff --git a/model/src/main/java/dev/msfjarvis/lobsters/data/api/KtorLobstersApi.kt b/model/src/main/java/dev/msfjarvis/lobsters/data/api/KtorLobstersApi.kt deleted file mode 100644 index 024a3a51..00000000 --- a/model/src/main/java/dev/msfjarvis/lobsters/data/api/KtorLobstersApi.kt +++ /dev/null @@ -1,16 +0,0 @@ -package dev.msfjarvis.lobsters.data.api - -import dev.msfjarvis.lobsters.data.api.LobstersApi.Companion.BASE_URL -import dev.msfjarvis.lobsters.model.LobstersPost -import io.ktor.client.HttpClient -import io.ktor.client.request.get -import javax.inject.Inject - -/** - * Ktor backed implementation of [LobstersApi] - */ -class KtorLobstersApi @Inject constructor(private val client: HttpClient) : LobstersApi { - override suspend fun getHottestPosts(page: Int): List { - return client.get("${BASE_URL}/hottest.json?page=$page") - } -} diff --git a/model/src/main/java/dev/msfjarvis/lobsters/data/api/LobstersApi.kt b/model/src/main/java/dev/msfjarvis/lobsters/data/api/LobstersApi.kt index 92651473..b924fb3f 100644 --- a/model/src/main/java/dev/msfjarvis/lobsters/data/api/LobstersApi.kt +++ b/model/src/main/java/dev/msfjarvis/lobsters/data/api/LobstersApi.kt @@ -1,13 +1,16 @@ package dev.msfjarvis.lobsters.data.api import dev.msfjarvis.lobsters.model.LobstersPost +import retrofit2.http.GET +import retrofit2.http.Query /** * Simple interface defining an API for lobste.rs */ interface LobstersApi { - suspend fun getHottestPosts(page: Int): List + @GET("hottest.json") + suspend fun getHottestPosts(@Query("page") page: Int): List companion object { const val BASE_URL = "https://lobste.rs" diff --git a/model/src/main/java/dev/msfjarvis/lobsters/injection/ApiModule.kt b/model/src/main/java/dev/msfjarvis/lobsters/injection/ApiModule.kt new file mode 100644 index 00000000..791f6bd4 --- /dev/null +++ b/model/src/main/java/dev/msfjarvis/lobsters/injection/ApiModule.kt @@ -0,0 +1,44 @@ +package dev.msfjarvis.lobsters.injection + +import com.squareup.moshi.Moshi +import dagger.Module +import dagger.Lazy +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ActivityComponent +import dev.msfjarvis.lobsters.data.api.LobstersApi +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import retrofit2.create + +@Module +@InstallIn(ActivityComponent::class) +object ApiModule { + @Provides + fun provideClient(): OkHttpClient { + return OkHttpClient.Builder() + .build() + } + + /** + * Using [Lazy] here is a trick I picked up from Zac Sweers, which he explained in more + * detail here: https://www.zacsweers.dev/dagger-party-tricks-deferred-okhttp-init/ + */ + @Provides + fun provideRetrofit( + client: Lazy, + moshi: Lazy, + ): Retrofit { + return Retrofit.Builder() + .client(client.get()) + .baseUrl(LobstersApi.BASE_URL) + .addConverterFactory(MoshiConverterFactory.create(moshi.get())) + .build() + } + + @Provides + fun provideApi(retrofit: Retrofit): LobstersApi { + return retrofit.create() + } +} diff --git a/model/src/main/java/dev/msfjarvis/lobsters/injection/KtorApiModule.kt b/model/src/main/java/dev/msfjarvis/lobsters/injection/KtorApiModule.kt deleted file mode 100644 index e4684add..00000000 --- a/model/src/main/java/dev/msfjarvis/lobsters/injection/KtorApiModule.kt +++ /dev/null @@ -1,14 +0,0 @@ -package dev.msfjarvis.lobsters.injection - -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.android.components.ActivityComponent -import dev.msfjarvis.lobsters.data.api.KtorLobstersApi -import dev.msfjarvis.lobsters.data.api.LobstersApi - -@Module -@InstallIn(ActivityComponent::class) -abstract class KtorApiModule { - @Binds abstract fun bindLobstersApi(realApi: KtorLobstersApi): LobstersApi -} diff --git a/model/src/main/java/dev/msfjarvis/lobsters/injection/KtorClientModule.kt b/model/src/main/java/dev/msfjarvis/lobsters/injection/KtorClientModule.kt deleted file mode 100644 index 642c1275..00000000 --- a/model/src/main/java/dev/msfjarvis/lobsters/injection/KtorClientModule.kt +++ /dev/null @@ -1,26 +0,0 @@ -package dev.msfjarvis.lobsters.injection - -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import io.ktor.client.HttpClient -import io.ktor.client.engine.okhttp.OkHttp -import io.ktor.client.features.json.JsonFeature -import io.ktor.client.features.json.serializer.KotlinxSerializer - -@Module -@InstallIn(SingletonComponent::class) -object KtorClientModule { - @Provides - fun provideClient() = HttpClient(OkHttp) { - install(JsonFeature) { - serializer = KotlinxSerializer() - } - engine { - config { - followSslRedirects(true) - } - } - } -} diff --git a/model/src/main/java/dev/msfjarvis/lobsters/injection/MoshiModule.kt b/model/src/main/java/dev/msfjarvis/lobsters/injection/MoshiModule.kt new file mode 100644 index 00000000..075f5ff5 --- /dev/null +++ b/model/src/main/java/dev/msfjarvis/lobsters/injection/MoshiModule.kt @@ -0,0 +1,16 @@ +package dev.msfjarvis.lobsters.injection + +import com.squareup.moshi.Moshi +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +object MoshiModule { + @Provides + fun provideMoshi(): Moshi { + return Moshi.Builder().build() + } +} diff --git a/model/src/main/java/dev/msfjarvis/lobsters/model/KeybaseSignature.kt b/model/src/main/java/dev/msfjarvis/lobsters/model/KeybaseSignature.kt index 77a3b001..6002a693 100644 --- a/model/src/main/java/dev/msfjarvis/lobsters/model/KeybaseSignature.kt +++ b/model/src/main/java/dev/msfjarvis/lobsters/model/KeybaseSignature.kt @@ -1,12 +1,12 @@ package dev.msfjarvis.lobsters.model -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass -@Serializable +@JsonClass(generateAdapter = true) class KeybaseSignature( - @SerialName("kb_username") + @Json(name = "kb_username") val kbUsername: String, - @SerialName("sig_hash") + @Json(name = "sig_hash") val sigHash: String ) diff --git a/model/src/main/java/dev/msfjarvis/lobsters/model/LobstersPost.kt b/model/src/main/java/dev/msfjarvis/lobsters/model/LobstersPost.kt index 4a617b12..f6d947bd 100644 --- a/model/src/main/java/dev/msfjarvis/lobsters/model/LobstersPost.kt +++ b/model/src/main/java/dev/msfjarvis/lobsters/model/LobstersPost.kt @@ -1,26 +1,26 @@ package dev.msfjarvis.lobsters.model -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass -@Serializable +@JsonClass(generateAdapter = true) class LobstersPost( - @SerialName("short_id") + @Json(name = "short_id") val shortId: String, - @SerialName("short_id_url") + @Json(name = "short_id_url") val shortIdUrl: String, - @SerialName("created_at") + @Json(name = "created_at") val createdAt: String, val title: String, val url: String, val score: Long, val flags: Long, - @SerialName("comment_count") + @Json(name = "comment_count") val commentCount: Long, val description: String, - @SerialName("comments_url") + @Json(name = "comments_url") val commentsUrl: String, - @SerialName("submitter_user") + @Json(name = "submitter_user") val submitterUser: Submitter, val tags: List, ) diff --git a/model/src/main/java/dev/msfjarvis/lobsters/model/Submitter.kt b/model/src/main/java/dev/msfjarvis/lobsters/model/Submitter.kt index a98ce55b..d00de4a7 100644 --- a/model/src/main/java/dev/msfjarvis/lobsters/model/Submitter.kt +++ b/model/src/main/java/dev/msfjarvis/lobsters/model/Submitter.kt @@ -1,27 +1,27 @@ package dev.msfjarvis.lobsters.model -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass -@Serializable +@JsonClass(generateAdapter = true) class Submitter( val username: String, - @SerialName("created_at") + @Json(name = "created_at") val createdAt: String, - @SerialName("is_admin") + @Json(name = "is_admin") val isAdmin: Boolean, val about: String, - @SerialName("is_moderator") + @Json(name = "is_moderator") val isModerator: Boolean, val karma: Long = 0, - @SerialName("avatar_url") + @Json(name = "avatar_url") val avatarUrl: String, - @SerialName("invited_by_user") + @Json(name = "invited_by_user") val invitedByUser: String, - @SerialName("github_username") + @Json(name = "github_username") val githubUsername: String? = null, - @SerialName("twitter_username") + @Json(name = "twitter_username") val twitterUsername: String? = null, - @SerialName("keybase_signatures") + @Json(name = "keybase_signatures") val keybaseSignatures: List = emptyList() ) diff --git a/model/src/test/java/dev/msfjarvis/lobsters/data/api/KtorLobstersApiTest.kt b/model/src/test/java/dev/msfjarvis/lobsters/data/api/LobstersApiTest.kt similarity index 51% rename from model/src/test/java/dev/msfjarvis/lobsters/data/api/KtorLobstersApiTest.kt rename to model/src/test/java/dev/msfjarvis/lobsters/data/api/LobstersApiTest.kt index c7fa3f3e..2dfe6a26 100644 --- a/model/src/test/java/dev/msfjarvis/lobsters/data/api/KtorLobstersApiTest.kt +++ b/model/src/test/java/dev/msfjarvis/lobsters/data/api/LobstersApiTest.kt @@ -1,54 +1,46 @@ package dev.msfjarvis.lobsters.data.api +import dev.msfjarvis.lobsters.injection.ApiModule +import dev.msfjarvis.lobsters.injection.MoshiModule import dev.msfjarvis.lobsters.util.TestUtils -import io.ktor.client.HttpClient -import io.ktor.client.engine.mock.MockEngine -import io.ktor.client.engine.mock.respond -import io.ktor.client.features.json.JsonFeature -import io.ktor.client.features.json.serializer.KotlinxSerializer -import io.ktor.http.fullPath -import io.ktor.http.headersOf import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import kotlinx.coroutines.runBlocking +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import okhttp3.mockwebserver.RecordedRequest import org.junit.AfterClass import org.junit.BeforeClass import org.junit.Test -class KtorLobstersApiTest { +class LobstersApiTest { companion object { - @JvmStatic - private lateinit var client: HttpClient - @JvmStatic - private lateinit var apiClient: LobstersApi + private val webServer = MockWebServer() + private val apiData = TestUtils.getJson("hottest.json") + private val okHttp = ApiModule.provideClient() + private val retrofit = ApiModule.provideRetrofit( + { okHttp }, + { MoshiModule.provideMoshi() } + ) + private val apiClient = ApiModule.provideApi(retrofit) @JvmStatic @BeforeClass fun setUp() { - client = HttpClient(MockEngine) { - install(JsonFeature) { - serializer = KotlinxSerializer() - } - engine { - addHandler { request -> - when (request.url.fullPath) { - "/hottest.json?page=1" -> { - val responseHeaders = headersOf("Content-Type" to listOf("application/json")) - respond(TestUtils.getJson("hottest.json"), headers = responseHeaders) - } - else -> error("Unhandled ${request.url.fullPath}") - } - } + webServer.start(8080) + webServer.dispatcher = object : Dispatcher() { + override fun dispatch(request: RecordedRequest): MockResponse { + return MockResponse().setBody(apiData).setResponseCode(200) } } - apiClient = KtorLobstersApi(client) } @JvmStatic @AfterClass fun tearDown() { - client.close() + webServer.shutdown() } }