mirror of
https://github.com/msfjarvis/compose-lobsters
synced 2025-08-14 08:17:04 +05:30
refactor(api): adopt EitherNet
This commit is contained in:
parent
484fac5779
commit
8652d4ceaa
8 changed files with 56 additions and 23 deletions
|
@ -1,6 +1,8 @@
|
|||
package dev.msfjarvis.claw.android.injection
|
||||
|
||||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
import com.slack.eithernet.ApiResultCallAdapterFactory
|
||||
import com.slack.eithernet.ApiResultConverterFactory
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
@ -26,7 +28,9 @@ object ApiModule {
|
|||
return Retrofit.Builder()
|
||||
.client(client)
|
||||
.baseUrl(LobstersApi.BASE_URL)
|
||||
.addConverterFactory(ApiResultConverterFactory)
|
||||
.addConverterFactory(json.asConverterFactory(contentType))
|
||||
.addCallAdapterFactory(ApiResultCallAdapterFactory)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
|
|
@ -2,11 +2,14 @@ package dev.msfjarvis.claw.android.paging
|
|||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import com.slack.eithernet.ApiResult.Failure
|
||||
import com.slack.eithernet.ApiResult.Success
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import dev.msfjarvis.claw.android.injection.IODispatcher
|
||||
import dev.msfjarvis.claw.model.LobstersPost
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
@ -17,19 +20,19 @@ constructor(
|
|||
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
) : PagingSource<Int, LobstersPost>() {
|
||||
|
||||
@Suppress("TooGenericExceptionCaught") // Intentional
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, LobstersPost> {
|
||||
return try {
|
||||
val page = params.key ?: 1
|
||||
val posts = withContext(ioDispatcher) { remoteFetcher.getItemsAtPage(page) }
|
||||
|
||||
LoadResult.Page(
|
||||
data = posts,
|
||||
prevKey = if (page == 1) null else page - 1,
|
||||
nextKey = page.plus(1)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
LoadResult.Error(e)
|
||||
val page = params.key ?: 1
|
||||
return when (val result = withContext(ioDispatcher) { remoteFetcher.getItemsAtPage(page) }) {
|
||||
is Success ->
|
||||
LoadResult.Page(
|
||||
data = result.value,
|
||||
prevKey = if (page == 1) null else page - 1,
|
||||
nextKey = page.plus(1)
|
||||
)
|
||||
is Failure.NetworkFailure -> LoadResult.Error(result.error)
|
||||
is Failure.UnknownFailure -> LoadResult.Error(result.error)
|
||||
is Failure.HttpFailure,
|
||||
is Failure.ApiFailure -> LoadResult.Error(IOException("API returned an invalid response"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package dev.msfjarvis.claw.android.paging
|
||||
|
||||
import com.slack.eithernet.ApiResult
|
||||
|
||||
/** SAM interface to abstract over a remote API that fetches paginated content. */
|
||||
fun interface RemoteFetcher<T> {
|
||||
suspend fun getItemsAtPage(page: Int): List<T>
|
||||
suspend fun getItemsAtPage(page: Int): ApiResult<List<T>, Unit>
|
||||
}
|
||||
|
|
|
@ -4,12 +4,15 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import com.slack.eithernet.ApiResult.Failure
|
||||
import com.slack.eithernet.ApiResult.Success
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dev.msfjarvis.claw.android.injection.IODispatcher
|
||||
import dev.msfjarvis.claw.android.paging.LobstersPagingSource
|
||||
import dev.msfjarvis.claw.android.ui.toLocalDateTime
|
||||
import dev.msfjarvis.claw.api.LobstersApi
|
||||
import dev.msfjarvis.claw.database.local.SavedPost
|
||||
import java.io.IOException
|
||||
import java.time.Month
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
|
@ -73,12 +76,28 @@ constructor(
|
|||
|
||||
suspend fun getPostComments(postId: String) =
|
||||
withContext(ioDispatcher) {
|
||||
val details = api.getPostDetails(postId)
|
||||
val details =
|
||||
when (val result = api.getPostDetails(postId)) {
|
||||
is Success -> result.value
|
||||
is Failure.NetworkFailure -> throw result.error
|
||||
is Failure.UnknownFailure -> throw result.error
|
||||
is Failure.HttpFailure,
|
||||
is Failure.ApiFailure -> throw IOException("API returned an invalid response")
|
||||
}
|
||||
val extendedDetails = postDetailsRepository.getExtendedDetails(details)
|
||||
extendedDetails
|
||||
}
|
||||
|
||||
suspend fun getUserProfile(username: String) = withContext(ioDispatcher) { api.getUser(username) }
|
||||
suspend fun getUserProfile(username: String) =
|
||||
withContext(ioDispatcher) {
|
||||
when (val result = api.getUser(username)) {
|
||||
is Success -> result.value
|
||||
is Failure.NetworkFailure -> throw result.error
|
||||
is Failure.UnknownFailure -> throw result.error
|
||||
is Failure.HttpFailure,
|
||||
is Failure.ApiFailure -> throw IOException("API returned an invalid response")
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshHottestPosts() {
|
||||
hottestPostsPagingSource?.invalidate()
|
||||
|
|
|
@ -4,13 +4,13 @@ import android.content.Context
|
|||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.slack.eithernet.ApiResult
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import dev.msfjarvis.claw.android.viewmodel.SavedPostsRepository
|
||||
import dev.msfjarvis.claw.api.LobstersApi
|
||||
import dev.msfjarvis.claw.common.posts.toDbModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
|
@ -23,7 +23,6 @@ import kotlinx.coroutines.flow.first
|
|||
* and for new-enough posts that are still getting comments to have an accurate one.
|
||||
*/
|
||||
@Suppress("DEPRECATION") // We're being nasty
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@HiltWorker
|
||||
class SavedPostUpdaterWorker
|
||||
@AssistedInject
|
||||
|
@ -39,8 +38,8 @@ constructor(
|
|||
.map { post ->
|
||||
CoroutineScope(coroutineContext + Job()).async {
|
||||
val details = runCatching { lobstersApi.getPostDetails(post.shortId) }.getOrNull()
|
||||
if (details != null) {
|
||||
savedPostsRepository.savePost(details.toDbModel())
|
||||
if (details is ApiResult.Success) {
|
||||
savedPostsRepository.savePost(details.value.toDbModel())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ plugins {
|
|||
dependencies {
|
||||
api(projects.model)
|
||||
api(libs.retrofit.lib)
|
||||
api(libs.eithernet)
|
||||
implementation(libs.kotlinx.serialization.core)
|
||||
testImplementation(libs.kotlinx.coroutines.core)
|
||||
testImplementation(kotlin("test-junit"))
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package dev.msfjarvis.claw.api
|
||||
|
||||
import com.slack.eithernet.ApiResult
|
||||
import dev.msfjarvis.claw.model.LobstersPost
|
||||
import dev.msfjarvis.claw.model.LobstersPostDetails
|
||||
import dev.msfjarvis.claw.model.User
|
||||
|
@ -10,14 +11,17 @@ import retrofit2.http.Query
|
|||
/** Simple interface defining an API for lobste.rs */
|
||||
interface LobstersApi {
|
||||
|
||||
@GET("hottest.json") suspend fun getHottestPosts(@Query("page") page: Int): List<LobstersPost>
|
||||
@GET("hottest.json")
|
||||
suspend fun getHottestPosts(@Query("page") page: Int): ApiResult<List<LobstersPost>, Unit>
|
||||
|
||||
@GET("newest.json") suspend fun getNewestPosts(@Query("page") page: Int): List<LobstersPost>
|
||||
@GET("newest.json")
|
||||
suspend fun getNewestPosts(@Query("page") page: Int): ApiResult<List<LobstersPost>, Unit>
|
||||
|
||||
@GET("s/{postId}.json")
|
||||
suspend fun getPostDetails(@Path("postId") postId: String): LobstersPostDetails
|
||||
suspend fun getPostDetails(@Path("postId") postId: String): ApiResult<LobstersPostDetails, Unit>
|
||||
|
||||
@GET("u/{username}.json") suspend fun getUser(@Path("username") username: String): User
|
||||
@GET("u/{username}.json")
|
||||
suspend fun getUser(@Path("username") username: String): ApiResult<User, Unit>
|
||||
|
||||
companion object {
|
||||
const val BASE_URL = "https://lobste.rs"
|
||||
|
|
|
@ -67,6 +67,7 @@ crux = "com.chimbori.crux:crux:3.9.1"
|
|||
dagger-hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "dagger" }
|
||||
dagger-hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "dagger" }
|
||||
dagger-hilt-core = { module = "com.google.dagger:hilt-core", version.ref = "dagger" }
|
||||
eithernet = "com.slack.eithernet:eithernet:1.2.1"
|
||||
javapoet = "com.squareup:javapoet:1.13.0"
|
||||
jsoup = "org.jsoup:jsoup:1.15.3"
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue