Skip to content

Commit

Permalink
feature: Integrate Room DB to store and fetch the LabResponse
Browse files Browse the repository at this point in the history
  • Loading branch information
ShivamNagpal committed Apr 15, 2023
1 parent a6ae7e8 commit e03297e
Show file tree
Hide file tree
Showing 18 changed files with 240 additions and 28 deletions.
5 changes: 5 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'androidx.navigation.safeargs'

Expand Down Expand Up @@ -39,6 +41,9 @@ dependencies {
implementation "androidx.navigation:navigation-fragment-ktx:$versions.navigation"
implementation "androidx.navigation:navigation-ui-ktx:$versions.navigation"

implementation "androidx.room:room-runtime:$versions.room"
kapt "androidx.room:room-compiler:$versions.room"

// https://mvnrepository.com/artifact/com.fasterxml.jackson.module/jackson-module-kotlin
implementation "com.fasterxml.jackson.module:jackson-module-kotlin:$versions.jackson_module_kotlin"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,42 @@
package com.nagpal.shivam.vtucslab

import androidx.multidex.MultiDexApplication
import androidx.room.Room
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.nagpal.shivam.vtucslab.data.local.AppDatabase
import com.nagpal.shivam.vtucslab.repositories.VtuCsLabRepository
import com.nagpal.shivam.vtucslab.repositories.VtuCsLabRepositoryImpl
import com.nagpal.shivam.vtucslab.services.VtuCsLabService
import com.nagpal.shivam.vtucslab.utilities.Constants.VTU_CS_LAB
import com.nagpal.shivam.vtucslab.utilities.StaticMethods

class VTUCSLabApplication : MultiDexApplication() {
val vtuCsLabRepository: VtuCsLabRepository =
VtuCsLabRepositoryImpl(this, VtuCsLabService.instance)
private lateinit var _db: AppDatabase
val db: AppDatabase
get() = _db

private lateinit var _vtuCsLabRepository: VtuCsLabRepository
val vtuCsLabRepository: VtuCsLabRepository
get() = _vtuCsLabRepository

override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(false)
}

_db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java,
VTU_CS_LAB
).build()

val vtuCsLabService = VtuCsLabService.instance
_vtuCsLabRepository = VtuCsLabRepositoryImpl(
this,
vtuCsLabService,
_db.labResponseDao(),
StaticMethods.jsonMapper
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.nagpal.shivam.vtucslab.data.local

import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters

@Database(entities = [LabResponse::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun labResponseDao(): LabResponseDao
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.nagpal.shivam.vtucslab.data.local

import androidx.room.TypeConverter
import java.util.*

class Converters {
@TypeConverter
fun dateFromTimestamp(value: Long?): Date? {
return value?.let { Date(it) }
}

@TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.nagpal.shivam.vtucslab.data.local

object Tables {
const val LAB_RESPONSE = "lab_response"
}

object LabResponseAttributes {
const val URL = "url"
const val RESPONSE = "response"
const val RESPONSE_TYPE = "response_type"
const val FETCHED_AT = "fetched_at"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.nagpal.shivam.vtucslab.data.local

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.nagpal.shivam.vtucslab.data.local.LabResponseAttributes.FETCHED_AT
import com.nagpal.shivam.vtucslab.data.local.LabResponseAttributes.RESPONSE
import com.nagpal.shivam.vtucslab.data.local.LabResponseAttributes.RESPONSE_TYPE
import com.nagpal.shivam.vtucslab.data.local.LabResponseAttributes.URL
import com.nagpal.shivam.vtucslab.data.local.Tables.LAB_RESPONSE
import java.util.*

@Entity(LAB_RESPONSE)
data class LabResponse(
@PrimaryKey @ColumnInfo(name = URL) val url: String,
@ColumnInfo(name = RESPONSE) val response: String,
@ColumnInfo(name = RESPONSE_TYPE) val responseType: LabResponseType,
@ColumnInfo(name = FETCHED_AT) val fetchedAt: Date,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.nagpal.shivam.vtucslab.data.local

import androidx.room.Dao
import androidx.room.Query
import androidx.room.Upsert
import com.nagpal.shivam.vtucslab.data.local.LabResponseAttributes.URL
import com.nagpal.shivam.vtucslab.data.local.Tables.LAB_RESPONSE

@Dao
interface LabResponseDao {
@Upsert
fun upsert(labResponse: LabResponse)

@Query("SELECT * FROM $LAB_RESPONSE where $URL = :url")
fun findByUrl(url: String): LabResponse?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.nagpal.shivam.vtucslab.data.local

enum class LabResponseType {
LABORATORY,
EXPERIMENT,
CONTENT,
}
Original file line number Diff line number Diff line change
@@ -1,65 +1,150 @@
package com.nagpal.shivam.vtucslab.repositories

import android.app.Application
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.databind.json.JsonMapper
import com.nagpal.shivam.vtucslab.core.ErrorType
import com.nagpal.shivam.vtucslab.core.Resource
import com.nagpal.shivam.vtucslab.data.local.LabResponse
import com.nagpal.shivam.vtucslab.data.local.LabResponseDao
import com.nagpal.shivam.vtucslab.data.local.LabResponseType
import com.nagpal.shivam.vtucslab.models.LaboratoryExperimentResponse
import com.nagpal.shivam.vtucslab.models.LaboratoryResponse
import com.nagpal.shivam.vtucslab.retrofit.ApiResult
import com.nagpal.shivam.vtucslab.services.VtuCsLabService
import com.nagpal.shivam.vtucslab.utilities.Configurations
import com.nagpal.shivam.vtucslab.utilities.NetworkUtils
import com.nagpal.shivam.vtucslab.utilities.StaticMethods
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.flow
import java.util.Date

private val LOG_TAG: String = VtuCsLabRepositoryImpl::class.java.name

class VtuCsLabRepositoryImpl(
private val application: Application,
private val vtuCsLabService: VtuCsLabService
private val vtuCsLabService: VtuCsLabService,
private val labResponseDao: LabResponseDao,
private val jsonMapper: JsonMapper,
) : VtuCsLabRepository {
override fun fetchLaboratories(url: String): Flow<Resource<LaboratoryResponse, ErrorType>> =
flow {
fetch(url) {
vtuCsLabService.getLaboratoryResponse(it)
fetch(
flow = this,
url,
LabResponseType.LABORATORY,
vtuCsLabService::getLaboratoryResponse,
{ data -> jsonMapper.writeValueAsString(data) }
) { stringContent ->
jsonMapper.readValue(
stringContent,
LaboratoryResponse::class.java
)
}
}

override fun fetchExperiments(url: String): Flow<Resource<LaboratoryExperimentResponse, ErrorType>> =
flow {
fetch(url) {
vtuCsLabService.getLaboratoryExperimentsResponse(it)
fetch(
flow = this,
url,
LabResponseType.EXPERIMENT,
vtuCsLabService::getLaboratoryExperimentsResponse,
{ data -> jsonMapper.writeValueAsString(data) }
) { stringContent ->
jsonMapper.readValue(
stringContent,
LaboratoryExperimentResponse::class.java
)
}
}

override fun fetchContent(url: String): Flow<Resource<String, ErrorType>> = flow {
fetch(url) {
vtuCsLabService.fetchRawResponse(it)
}
fetch(
flow = this,
url,
LabResponseType.CONTENT,
vtuCsLabService::fetchRawResponse,
{ stringContent -> stringContent }
) { stringContent -> stringContent }
}

private suspend fun <D : Any> FlowCollector<Resource<D, ErrorType>>.fetch(
private suspend fun <D : Any> fetch(
flow: FlowCollector<Resource<D, ErrorType>>,
url: String,
executable: suspend (String) -> ApiResult<D>
labResponseType: LabResponseType,
fetchFromNetwork: suspend (String) -> ApiResult<D>,
encodeToString: (D) -> String,
decodeFromString: (String) -> D,
) {
emit(Resource.Loading())
flow.emit(Resource.Loading())

val labResponse = labResponseDao.findByUrl(url)
var foundInDB = false
labResponse?.let {
try {
flow.emit(Resource.Success(decodeFromString.invoke(it.response)))
foundInDB = true
if (it.fetchedAt.after(
StaticMethods.getCurrentDateMinusSeconds(Configurations.RESPONSE_FRESHNESS_TIME)
)
) {
return
}
} catch (_: JsonParseException) {
}
}

if (!NetworkUtils.isNetworkConnected(application)) {
emit(Resource.Error(ErrorType.NoActiveInternetConnection))
emitNetworkErrors(
flow,
foundInDB,
Resource.Error(ErrorType.NoActiveInternetConnection),
)
return
}
when (val apiResult = executable.invoke(url)) {
when (val apiResult = fetchFromNetwork.invoke(url)) {
is ApiResult.ApiSuccess -> {
emit(Resource.Success(apiResult.data))
val data = apiResult.data
labResponseDao.upsert(
LabResponse(
url,
encodeToString(data),
labResponseType,
Date(),
)
)
flow.emit(Resource.Success(data))
}

is ApiResult.ApiError -> {
StaticMethods.logNetworkResultError(LOG_TAG, url, apiResult.code, apiResult.message)
emit(Resource.Error(ErrorType.SomeErrorOccurred))
emitNetworkErrors(
flow,
foundInDB,
Resource.Error(ErrorType.SomeErrorOccurred),
)
}

is ApiResult.ApiException -> {
StaticMethods.logNetworkResultException(LOG_TAG, url, apiResult.throwable)
emit(Resource.Error(ErrorType.SomeErrorOccurred))
emitNetworkErrors(
flow,
foundInDB,
Resource.Error(ErrorType.SomeErrorOccurred),
)
}
}
}

private suspend fun <D : Any> emitNetworkErrors(
flow: FlowCollector<Resource<D, ErrorType>>,
foundInDB: Boolean,
errorResource: Resource.Error<D, ErrorType>
) {
if (!foundInDB) {
flow.emit(errorResource)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.nagpal.shivam.vtucslab.utilities

object Configurations {
const val RESPONSE_FRESHNESS_TIME = 3 * 60 * 60
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ object Constants {
"https://raw.githubusercontent.com/vtucs/Index_v3/master/Index_v3.json"
const val LABEL_CODE = "Code"
const val GITHUB_RAW_CONTENT = "github_raw_content"
const val VTU_CS_LAB = "vtu_cs_lab"
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import com.nagpal.shivam.vtucslab.models.LaboratoryExperimentResponse
import com.nagpal.shivam.vtucslab.models.LaboratoryResponse
import java.util.*

object StaticMethods {

Expand Down Expand Up @@ -49,4 +50,10 @@ object StaticMethods {
throwable
)
}

fun getCurrentDateMinusSeconds(seconds: Int): Date {
val calendar = Calendar.getInstance()
calendar.add(Calendar.SECOND, -seconds)
return calendar.time
}
}
4 changes: 1 addition & 3 deletions app/src/main/res/layout/fragment_display.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@

<TextView
android:id="@+id/empty_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
style="@style/ErrorMessageTextView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
Expand Down
4 changes: 1 addition & 3 deletions app/src/main/res/layout/fragment_program.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@

<TextView
android:id="@+id/empty_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
style="@style/ErrorMessageTextView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
Expand Down
4 changes: 1 addition & 3 deletions app/src/main/res/layout/fragment_repository.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@

<TextView
android:id="@+id/empty_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
style="@style/ErrorMessageTextView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<resources>
<string name="app_name">VTU CS Lab</string>
<string name="app_name_new">VTU CS Lab New Version</string>
<string name="no_internet_connection">No Internet Connection.</string>
<string name="no_internet_connection">No Internet Connection.\n\nThis content is not available offline yet, Kindly connect to the internet to sync this content locally.</string>
<string name="error_occurred">Some Error Occurred.</string>
<string name="copy">Copy</string>
<string name="git_repository">Git Repository</string>
Expand Down
Loading

0 comments on commit e03297e

Please sign in to comment.