From 276877119dc629fcad1e1dd5fdce70467d5bb0e9 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Sun, 8 Nov 2020 16:41:02 +0530 Subject: [PATCH] app: reinstate API wrapper to hide client implementation Signed-off-by: Harsh Shandilya --- app/proguard-rules.pro | 8 ++++++ .../lobsters/data/api/KtorLobstersApi.kt | 16 ++++++++++++ .../lobsters/data/api/LobstersApi.kt | 15 +++++++++++ .../lobsters/injection/KtorApiModule.kt | 26 +++++-------------- .../lobsters/injection/KtorClientModule.kt | 26 +++++++++++++++++++ .../ui/viewmodel/LobstersViewModel.kt | 7 +++-- 6 files changed, 75 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/dev/msfjarvis/lobsters/data/api/KtorLobstersApi.kt create mode 100644 app/src/main/java/dev/msfjarvis/lobsters/data/api/LobstersApi.kt create mode 100644 app/src/main/java/dev/msfjarvis/lobsters/injection/KtorClientModule.kt diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index f1e70065..e75e421b 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -8,3 +8,11 @@ -keepclasseswithmembers class dev.msfjarvis.lobsters.model.** { kotlinx.serialization.KSerializer serializer(...); } + +# Workaround for https://github.com/ktorio/ktor/issues/1354 +-keepclassmembers class io.ktor.** { volatile ; } + +# Workaround for https://github.com/Kotlin/kotlinx.coroutines/issues/1564 +-keepclassmembers class kotlinx.** { + volatile ; +} diff --git a/app/src/main/java/dev/msfjarvis/lobsters/data/api/KtorLobstersApi.kt b/app/src/main/java/dev/msfjarvis/lobsters/data/api/KtorLobstersApi.kt new file mode 100644 index 00000000..024a3a51 --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/lobsters/data/api/KtorLobstersApi.kt @@ -0,0 +1,16 @@ +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/app/src/main/java/dev/msfjarvis/lobsters/data/api/LobstersApi.kt b/app/src/main/java/dev/msfjarvis/lobsters/data/api/LobstersApi.kt new file mode 100644 index 00000000..92651473 --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/lobsters/data/api/LobstersApi.kt @@ -0,0 +1,15 @@ +package dev.msfjarvis.lobsters.data.api + +import dev.msfjarvis.lobsters.model.LobstersPost + +/** + * Simple interface defining an API for lobste.rs + */ +interface LobstersApi { + + suspend fun getHottestPosts(page: Int): List + + companion object { + const val BASE_URL = "https://lobste.rs" + } +} diff --git a/app/src/main/java/dev/msfjarvis/lobsters/injection/KtorApiModule.kt b/app/src/main/java/dev/msfjarvis/lobsters/injection/KtorApiModule.kt index a9fad0f7..e4684add 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/injection/KtorApiModule.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/injection/KtorApiModule.kt @@ -1,26 +1,14 @@ package dev.msfjarvis.lobsters.injection +import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.components.ApplicationComponent -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 +import dagger.hilt.android.components.ActivityComponent +import dev.msfjarvis.lobsters.data.api.KtorLobstersApi +import dev.msfjarvis.lobsters.data.api.LobstersApi @Module -@InstallIn(ApplicationComponent::class) -object KtorApiModule { - @Provides - fun provideClient() = HttpClient(OkHttp) { - install(JsonFeature) { - serializer = KotlinxSerializer() - } - engine { - config { - followSslRedirects(true) - } - } - } +@InstallIn(ActivityComponent::class) +abstract class KtorApiModule { + @Binds abstract fun bindLobstersApi(realApi: KtorLobstersApi): LobstersApi } diff --git a/app/src/main/java/dev/msfjarvis/lobsters/injection/KtorClientModule.kt b/app/src/main/java/dev/msfjarvis/lobsters/injection/KtorClientModule.kt new file mode 100644 index 00000000..ce801075 --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/lobsters/injection/KtorClientModule.kt @@ -0,0 +1,26 @@ +package dev.msfjarvis.lobsters.injection + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ApplicationComponent +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(ApplicationComponent::class) +object KtorClientModule { + @Provides + fun provideClient() = HttpClient(OkHttp) { + install(JsonFeature) { + serializer = KotlinxSerializer() + } + engine { + config { + followSslRedirects(true) + } + } + } +} diff --git a/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt b/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt index ed33768a..5959f3f0 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt @@ -3,10 +3,9 @@ package dev.msfjarvis.lobsters.ui.viewmodel import androidx.hilt.lifecycle.ViewModelInject import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import dev.msfjarvis.lobsters.data.api.LobstersApi import dev.msfjarvis.lobsters.data.source.PostsDatabase import dev.msfjarvis.lobsters.model.LobstersPost -import io.ktor.client.HttpClient -import io.ktor.client.request.get import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -16,7 +15,7 @@ import java.net.SocketTimeoutException import java.net.UnknownHostException class LobstersViewModel @ViewModelInject constructor( - private val client: HttpClient, + private val lobstersApi: LobstersApi, database: PostsDatabase, ) : ViewModel() { private var apiPage = 1 @@ -57,7 +56,7 @@ class LobstersViewModel @ViewModelInject constructor( private fun getMorePostsInternal(firstLoad: Boolean) { viewModelScope.launch(coroutineExceptionHandler) { - val newPosts = client.get>("https://lobste.rs/hottest.json?page=$apiPage") + val newPosts = lobstersApi.getHottestPosts(apiPage) if (firstLoad) { _posts.value = newPosts postsDao.deleteAllPosts()