Sfoglia il codice sorgente

Added caching books in shared preferences

Vadik Sirekanyan 7 anni fa
parent
commit
994d72e946

+ 36 - 5
app/src/main/java/me/vadik/knigopis/BookRepository.kt

@@ -5,6 +5,8 @@ import io.reactivex.Single
 import io.reactivex.rxkotlin.Singles
 import me.vadik.knigopis.api.Endpoint
 import me.vadik.knigopis.auth.KAuth
+import me.vadik.knigopis.common.NetworkChecker
+import me.vadik.knigopis.data.BookCache
 import me.vadik.knigopis.data.BookOrganizer
 import me.vadik.knigopis.model.*
 
@@ -20,16 +22,25 @@ interface BookRepository {
 
 class BookRepositoryImpl(
     private val api: Endpoint,
+    private val cache: BookCache,
     private val auth: KAuth,
     private val plannedBookOrganizer: BookOrganizer<PlannedBook>,
-    private val finishedBookPrepare: BookOrganizer<FinishedBook>
+    private val finishedBookPrepare: BookOrganizer<FinishedBook>,
+    private val networkChecker: NetworkChecker
 ) : BookRepository {
 
     override fun loadBooks(): Single<List<Pair<Book, BookHeader>>> =
-        Singles.zip(
-            api.getPlannedBooks(auth.getAccessToken()),
-            api.getFinishedBooks(auth.getAccessToken())
-        ).map { (planned, finished) ->
+        if (networkChecker.isNetworkAvailable()) {
+            getFromNetwork()
+                .doOnSuccess { saveToCache(it).blockingAwait() }
+                .doOnError {
+                    logError("Cannot load books from network", it)
+                    logWarn("Getting cached books")
+                }
+                .onErrorResumeNext(findInCache())
+        } else {
+            findInCache()
+        }.map { (planned, finished) ->
             mutableListOf<Pair<Book, BookHeader>>().apply {
                 addAll(plannedBookOrganizer.organize(planned))
                 addAll(finishedBookPrepare.organize(finished))
@@ -58,4 +69,24 @@ class BookRepositoryImpl(
             }
         }
 
+    private fun getFromNetwork(): Single<Pair<List<PlannedBook>, List<FinishedBook>>> =
+        Singles.zip(
+            api.getPlannedBooks(auth.getAccessToken()),
+            api.getFinishedBooks(auth.getAccessToken())
+        )
+
+    private fun findInCache(): Single<Pair<List<PlannedBook>, List<FinishedBook>>> =
+        Singles.zip(
+            cache.getPlannedBooks().toSingle(),
+            cache.getFinishedBooks().toSingle()
+        )
+
+    private fun saveToCache(books: Pair<List<PlannedBook>, List<FinishedBook>>): Completable =
+        books.let { (planned, finished) ->
+            Completable.concatArray(
+                cache.savePlannedBooks(planned),
+                cache.saveFinishedBooks(finished)
+            )
+        }
+
 }

+ 43 - 0
app/src/main/java/me/vadik/knigopis/common/CommonCache.kt

@@ -0,0 +1,43 @@
+package me.vadik.knigopis.common
+
+import android.content.Context
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import io.reactivex.Completable
+import io.reactivex.Maybe
+import java.lang.reflect.Type
+
+private const val PREFS_NAME = "cache1"
+
+inline fun <reified T> genericType(): Type = object : TypeToken<T>() {}.type
+
+interface CommonCache {
+
+    fun <T> saveToJson(key: String, books: List<T>): Completable
+
+    fun <T> getFromJson(key: String, type: Type): Maybe<T>
+
+}
+
+class CommonCacheImpl(
+    context: Context,
+    private val gson: Gson
+) : CommonCache {
+
+    private val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+
+    override fun <T> getFromJson(key: String, type: Type): Maybe<T> =
+        Maybe.fromCallable {
+            prefs.getString(key, null)?.let { json ->
+                gson.fromJson<T>(json, type)
+            }
+        }
+
+    override fun <T> saveToJson(key: String, books: List<T>): Completable =
+        Completable.fromAction {
+            prefs.edit()
+                .putString(key, gson.toJson(books))
+                .apply()
+        }
+
+}

+ 39 - 0
app/src/main/java/me/vadik/knigopis/data/BookCache.kt

@@ -0,0 +1,39 @@
+package me.vadik.knigopis.data
+
+import io.reactivex.Completable
+import io.reactivex.Maybe
+import me.vadik.knigopis.common.CommonCache
+import me.vadik.knigopis.common.genericType
+import me.vadik.knigopis.model.FinishedBook
+import me.vadik.knigopis.model.PlannedBook
+
+private const val PLANNED_KEY = "planned"
+private const val FINISHED_KEY = "finished"
+
+interface BookCache {
+
+    fun getPlannedBooks(): Maybe<List<PlannedBook>>
+
+    fun getFinishedBooks(): Maybe<List<FinishedBook>>
+
+    fun savePlannedBooks(planned: List<PlannedBook>): Completable
+
+    fun saveFinishedBooks(finished: List<FinishedBook>): Completable
+
+}
+
+class BookCacheImpl(private val commonCache: CommonCache) : BookCache {
+
+    override fun getPlannedBooks(): Maybe<List<PlannedBook>> =
+        commonCache.getFromJson(PLANNED_KEY, genericType<List<PlannedBook>>())
+
+    override fun getFinishedBooks(): Maybe<List<FinishedBook>> =
+        commonCache.getFromJson(FINISHED_KEY, genericType<List<FinishedBook>>())
+
+    override fun savePlannedBooks(planned: List<PlannedBook>): Completable =
+        commonCache.saveToJson(PLANNED_KEY, planned)
+
+    override fun saveFinishedBooks(finished: List<FinishedBook>): Completable =
+        commonCache.saveToJson(FINISHED_KEY, finished)
+
+}

+ 20 - 11
app/src/main/java/me/vadik/knigopis/dependency/modules.kt

@@ -1,5 +1,6 @@
 package me.vadik.knigopis.dependency
 
+import com.google.gson.Gson
 import com.google.gson.GsonBuilder
 import me.vadik.knigopis.*
 import me.vadik.knigopis.api.BookCoverSearch
@@ -9,11 +10,8 @@ import me.vadik.knigopis.api.ImageEndpoint
 import me.vadik.knigopis.api.gson.ImageThumbnailDeserializer
 import me.vadik.knigopis.auth.KAuth
 import me.vadik.knigopis.auth.KAuthImpl
-import me.vadik.knigopis.common.ResourceProvider
-import me.vadik.knigopis.common.ResourceProviderImpl
-import me.vadik.knigopis.data.BookOrganizer
-import me.vadik.knigopis.data.FinishedBookPrepareImpl
-import me.vadik.knigopis.data.PlannedBookOrganizerImpl
+import me.vadik.knigopis.common.*
+import me.vadik.knigopis.data.*
 import me.vadik.knigopis.dialog.BottomSheetDialogFactory
 import me.vadik.knigopis.dialog.DialogFactory
 import me.vadik.knigopis.model.FinishedBook
@@ -34,15 +32,28 @@ private const val IMAGE_API_URL = "https://api.qwant.com/api/"
 private const val DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"
 
 val appModule = applicationContext {
-    bean { BookRepositoryImpl(get(), get(), get("planned"), get("finished")) as BookRepository }
+    bean {
+        BookRepositoryImpl(
+            get(),
+            get(),
+            get(),
+            get("planned"),
+            get("finished"),
+            get()
+        ) as BookRepository
+    }
     bean { BookCoverSearchImpl(get(), BookCoverCacheImpl(get())) as BookCoverSearch }
     bean { KAuthImpl(get(), get()) as KAuth }
-    bean { createMainEndpoint() }
+    bean { createMainEndpoint(get()) }
     bean { createImageEndpoint() }
     bean("planned") { PlannedBookOrganizerImpl(get(), get()) as BookOrganizer<PlannedBook> }
     bean("finished") { FinishedBookPrepareImpl(get()) as BookOrganizer<FinishedBook> }
     bean { ConfigurationImpl(get()) as Configuration }
     bean { ResourceProviderImpl(get()) as ResourceProvider }
+    bean { NetworkCheckerImpl(get()) as NetworkChecker }
+    bean { BookCacheImpl(get()) as BookCache }
+    bean { CommonCacheImpl(get(), get()) as CommonCache }
+    bean { GsonBuilder().setDateFormat(DATE_FORMAT).create() }
     factory { BottomSheetDialogFactory(it["activity"]) as DialogFactory }
     userModule()
 }
@@ -51,14 +62,12 @@ private fun Context.userModule() {
     bean { UserInteractorImpl(get(), get()) as UserInteractor }
 }
 
-private fun createMainEndpoint() =
+private fun createMainEndpoint(gson: Gson) =
     Retrofit.Builder()
         .baseUrl(MAIN_API_URL)
         .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
         .addConverterFactory(
-            GsonConverterFactory.create(
-                GsonBuilder().setDateFormat(DATE_FORMAT).create()
-            )
+            GsonConverterFactory.create(gson)
         )
         .client(
             OkHttpClient.Builder()