Skip to content

Instantly share code, notes, and snippets.

@cdongieux
Last active July 19, 2019 09:47
Show Gist options
  • Save cdongieux/f94451ca79143b499c413a3a9a7dac86 to your computer and use it in GitHub Desktop.
Save cdongieux/f94451ca79143b499c413a3a9a7dac86 to your computer and use it in GitHub Desktop.
@Database(entities = [Foo::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun fooDao(): FooDao
companion object {
fun buildDatabase(context: Context) =
Room
.databaseBuilder(context.applicationContext, AppDatabase::class.java, "AppDatabase.db")
.build()
}
}
@Dao
interface FooDao {
@Query("SELECT * FROM Foo WHERE id = :id LIMIT 1")
fun getFoo(id: Int): LiveData<Foo>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(data: Foo)
}
@Entity
data class Foo(
@PrimaryKey val id: Int,
val data: String?)
class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "MainActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val appDatabase = AppDatabase.buildDatabase(applicationContext)
val networkBoundResource = object : NetworkBoundResource<Foo>() {
override suspend fun loadFromDb(): LiveData<Foo> {
Log.d(TAG, "[${Thread.currentThread().name}] loadFromDb() called")
return appDatabase.fooDao().getFoo(42)
}
override fun shouldFetch(data: Foo?): Boolean {
Log.d(TAG, "[${Thread.currentThread().name}] shouldFetch() called: data = [$data]")
return data == null
}
override fun fetch(): Resource<Foo> {
Log.d(TAG, "[${Thread.currentThread().name}] fetch() called")
Thread.sleep(2500)
return Resource.Success(Foo(42, "data"))
}
override suspend fun saveToDb(data: Foo) {
Log.d(TAG, "[${Thread.currentThread().name}] saveToDb() called with: data = [$data]")
appDatabase.fooDao().insert(data)
}
}
networkBoundResource.asLiveData().observe(this, Observer {
Log.d(TAG, "[${Thread.currentThread().name}] onChanged() called with: value = [$it]")
})
}
}
abstract class NetworkBoundResource<ResultType> {
companion object {
const val TAG = "NetworkBoundResource"
const val DEBUG = true
}
private val ioScope = CoroutineScope(Dispatchers.IO)
fun asLiveData(): LiveData<Resource<ResultType>> = liveData<Resource<ResultType>> {
emit(Resource.Loading())
val liveDbResult = withContext(Dispatchers.IO) {
loadFromDb()
}
emitSource(Transformations.map(liveDbResult) {
Resource.Success(it)
})
liveDbResult.observeOnce {
ioScope.launch {
onDbResult(this@liveData, it)
}
}
}
private suspend fun onDbResult(liveDataScope: LiveDataScope<Resource<ResultType>>, dbResult: ResultType) {
if (!shouldFetch(dbResult)) return
val apiResponse = try {
fetch()
} catch (t: Throwable) {
Resource.Error(t)
}
when (apiResponse) {
is Resource.Success -> {
if (DEBUG ) Log.d(TAG, "API success")
apiResponse.getOrNull()?.let {
saveToDb(it)
}
}
is Resource.Error -> {
if (DEBUG ) Log.d(TAG, "API error: [${apiResponse.exception}]")
liveDataScope.emit(Resource.Error(apiResponse.exception))
}
}
}
protected abstract suspend fun loadFromDb(): LiveData<ResultType>
protected abstract fun shouldFetch(data: ResultType?): Boolean
protected abstract fun fetch(): Resource<ResultType>
protected abstract suspend fun saveToDb(data: ResultType)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment