Skip to content

Commit

Permalink
Data Layer Cleanup (#112)
Browse files Browse the repository at this point in the history
A lot of repackaging, renaming, and altering models for better clarity.
  • Loading branch information
Rahkeen authored Aug 14, 2024
1 parent 1feb882 commit d7a3c7a
Show file tree
Hide file tree
Showing 23 changed files with 234 additions and 239 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.emergetools.hackernews.data
package com.emergetools.hackernews

import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.runtime.Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,27 @@ import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import androidx.room.Room
import com.emergetools.hackernews.data.BookmarkDao
import com.emergetools.hackernews.data.HackerNewsBaseDataSource
import com.emergetools.hackernews.data.HackerNewsDatabase
import com.emergetools.hackernews.data.HackerNewsSearchClient
import com.emergetools.hackernews.data.HackerNewsWebClient
import com.emergetools.hackernews.data.ItemRepository
import com.emergetools.hackernews.data.LocalCookieJar
import com.emergetools.hackernews.data.UserStorage
import com.emergetools.hackernews.data.local.BookmarkDao
import com.emergetools.hackernews.data.local.HackerNewsDatabase
import com.emergetools.hackernews.data.local.LocalCookieJar
import com.emergetools.hackernews.data.local.UserStorage
import com.emergetools.hackernews.data.remote.HackerNewsBaseClient
import com.emergetools.hackernews.data.remote.HackerNewsSearchClient
import com.emergetools.hackernews.data.remote.HackerNewsWebClient
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import java.time.Duration

class HackerNewsApplication: Application() {
class HackerNewsApplication : Application() {
private val json = Json { ignoreUnknownKeys = true }

private lateinit var httpClient: OkHttpClient
private lateinit var baseClient: HackerNewsBaseDataSource

lateinit var bookmarkDao: BookmarkDao
lateinit var userStorage: UserStorage
lateinit var searchClient: HackerNewsSearchClient
lateinit var webClient: HackerNewsWebClient
lateinit var itemRepository: ItemRepository
lateinit var baseClient: HackerNewsBaseClient

override fun onCreate() {
super.onCreate()
Expand All @@ -47,17 +45,16 @@ class HackerNewsApplication: Application() {
.cookieJar(LocalCookieJar(userStorage))
.build()

baseClient = HackerNewsBaseDataSource(json, httpClient)
searchClient = HackerNewsSearchClient(json, httpClient)
webClient = HackerNewsWebClient(httpClient)
itemRepository = ItemRepository(baseClient)
baseClient = HackerNewsBaseClient(json, httpClient)
}
}

val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "user")

fun Context.itemRepository(): ItemRepository {
return (this.applicationContext as HackerNewsApplication).itemRepository
fun Context.baseClient(): HackerNewsBaseClient {
return (this.applicationContext as HackerNewsApplication).baseClient
}

fun Context.userStorage(): UserStorage {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.emergetools.hackernews.features.comments
package com.emergetools.hackernews

import android.graphics.Typeface
import android.text.Layout
Expand Down Expand Up @@ -151,4 +151,4 @@ private fun StyleSpan.toSpanStyle(): SpanStyle? {
private fun TypefaceSpan.toSpanStyle(): SpanStyle {
val fontFamily = this.typeface?.let { FontFamily(it) }
return SpanStyle(fontFamily = fontFamily)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import androidx.navigation.NavHostController
import androidx.navigation.Navigator
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import com.emergetools.hackernews.data.ChromeTabsProvider
import com.emergetools.hackernews.features.bookmarks.bookmarksRoutes
import com.emergetools.hackernews.features.comments.commentsRoutes
import com.emergetools.hackernews.features.login.loginRoutes
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.emergetools.hackernews.data
package com.emergetools.hackernews.data.local

import androidx.room.ColumnInfo
import androidx.room.Dao
Expand Down Expand Up @@ -36,7 +36,7 @@ interface BookmarkDao {
}

@Database(entities = [LocalBookmark::class], version = 1)
abstract class HackerNewsDatabase: RoomDatabase() {
abstract class HackerNewsDatabase : RoomDatabase() {
abstract fun bookmarkDao(): BookmarkDao
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.emergetools.hackernews.data
package com.emergetools.hackernews.data.local

import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
Expand Down Expand Up @@ -27,4 +27,4 @@ class LocalCookieJar(private val userStorage: UserStorage) : CookieJar {
emptyList()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.emergetools.hackernews.data
package com.emergetools.hackernews.data.local

import android.content.Context
import androidx.datastore.preferences.core.edit
Expand All @@ -25,4 +25,4 @@ class UserStorage(private val appContext: Context) {
fun getCookie(): Flow<String?> {
return appContext.dataStore.data.map { it[cookieKey] }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.emergetools.hackernews.data.remote

import retrofit2.http.GET
import retrofit2.http.Path

interface HackerNewsBaseApi {
@GET("topstories.json")
suspend fun getTopStoryIds(): List<Long>

@GET("newstories.json")
suspend fun getNewStoryIds(): List<Long>

@GET("item/{id}.json")
suspend fun getItem(@Path("id") itemId: Long): ItemResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package com.emergetools.hackernews.data.remote

import android.util.Log
import com.emergetools.hackernews.data.remote.ItemResponse.Item
import com.emergetools.hackernews.features.stories.FeedType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.kotlinx.serialization.asConverterFactory

typealias ItemId = Long
typealias Page = List<ItemId>

private const val BASE_FIREBASE_URL = "https://hacker-news.firebaseio.com/v0/"

@Serializable(with = ItemResponseSerializer::class)
sealed interface ItemResponse {
@Serializable
data class Item(
val id: Long,
val type: String,
val time: Long,
val by: String? = null,
val title: String? = null,
val score: Int? = null,
val url: String? = null,
val descendants: Int? = null,
val kids: List<Long>? = null,
val text: String? = null
) : ItemResponse

@Serializable
data object NullResponse : ItemResponse
}

private object ItemResponseSerializer :
JsonContentPolymorphicSerializer<ItemResponse>(ItemResponse::class) {
override fun selectDeserializer(element: JsonElement) = when {
element is JsonNull -> ItemResponse.NullResponse.serializer()
else -> Item.serializer()
}
}

sealed class FeedIdResponse(val page: Page) {
class Success(page: Page) : FeedIdResponse(page)
data class Error(val message: String) : FeedIdResponse(emptyList())
}

class HackerNewsBaseClient(
json: Json,
client: OkHttpClient,
) {
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_FIREBASE_URL)
.addConverterFactory(json.asConverterFactory("application/json; charset=UTF8".toMediaType()))
.client(client)
.build()

private val api = retrofit.create(HackerNewsBaseApi::class.java)

suspend fun getFeedIds(type: FeedType): FeedIdResponse {
return withContext(Dispatchers.IO) {
when (type) {
FeedType.Top -> {
try {
val result = api.getTopStoryIds()
FeedIdResponse.Success(result)
} catch (error: Exception) {
FeedIdResponse.Error(error.message.orEmpty())
}
}

FeedType.New -> {
try {
val result = api.getNewStoryIds()
FeedIdResponse.Success(result)
} catch (error: Exception) {
FeedIdResponse.Error(error.message.orEmpty())
}
}
}
}
}

suspend fun getPage(page: Page): List<Item> {
Log.d("Feed", "Loading Page: $page")
return withContext(Dispatchers.IO) {
val result = mutableListOf<Item>()
page.forEach { itemId ->
try {
val response = api.getItem(itemId)
if (response is Item) {
result.add(response)
}
} catch (_: Exception) {
}
}
result.toList()
}
}

suspend fun getItem(itemId: Long): ItemResponse {
return withContext(Dispatchers.IO) {
api.getItem(itemId)
}
}
}

fun MutableList<Page>.next() = removeAt(0)


Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.emergetools.hackernews.data
package com.emergetools.hackernews.data.remote

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
Expand Down
Loading

0 comments on commit d7a3c7a

Please sign in to comment.