Skip to content

Instantly share code, notes, and snippets.

@myungpyo
Last active February 14, 2022 15:06
Show Gist options
  • Save myungpyo/3528bb2ec7088cb2e81657b7661cef6c to your computer and use it in GitHub Desktop.
Save myungpyo/3528bb2ec7088cb2e81657b7661cef6c to your computer and use it in GitHub Desktop.
import kotlinx.coroutines.CancellationException
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Future
fun main() {
val viewModel = SampleViewModel(
userSettingRepository = UserSettingRepository(
localUserSettingDataSource = LocalUserSettingDataSource(),
remoteUserSettingDataSource = RemoteUserSettingDataSource()
)
)
viewModel.syncUserSetting()
// Emulate user exit after 5 seconds
Thread.sleep(5000)
viewModel.onClear()
}
class SampleViewModel(private val userSettingRepository: UserSettingRepository) {
private val userId = "TestUser#1"
private var activeSyncDisposable: Disposable? = null
fun syncUserSetting() {
Logger.d("SampleViewModel : syncUserSetting() - start")
activeSyncDisposable = userSettingRepository.syncUserSetting(userId, object : Callback<UserSetting> {
override fun onSuccess(value: UserSetting) {
Logger.d("SampleViewModel : syncUserSetting() : success : $value")
}
override fun onFail(throwable: Throwable) {
Logger.d("SampleViewModel : syncUserSetting() : failed : $throwable")
}
})
}
fun onClear() {
Logger.d("SampleViewModel : onClear()")
activeSyncDisposable?.dispose()
activeSyncDisposable = null
}
}
class UserSettingRepository(
private val localUserSettingDataSource: LocalUserSettingDataSource,
private val remoteUserSettingDataSource: RemoteUserSettingDataSource
) {
private val dispatcher: ExecutorService = Executors.newCachedThreadPool { runnable ->
Thread(runnable).apply {
isDaemon = true
}
}
fun syncUserSetting(userId: String, callback: Callback<UserSetting>): Disposable {
// Sync process
Logger.d("UserSettingRepository : syncUserSetting() - Fetch from remote data source")
return dispatcher.submit {
remoteUserSettingDataSource.loadUserSetting(userId, object : Callback<UserSetting> {
override fun onSuccess(value: UserSetting) {
if (Thread.interrupted()) {
callback.onFail(CancellationException())
return
}
val remoteUserSetting = value
Logger.d("UserSettingRepository : syncUserSetting() - Load from local data source")
localUserSettingDataSource.loadUserSetting(userId, object : Callback<UserSetting> {
override fun onSuccess(value: UserSetting) {
if (Thread.interrupted()) {
callback.onFail(CancellationException())
return
}
val localUserSetting = value
val updatedUserSetting = localUserSetting.fold(remoteUserSetting)
Logger.d("UserSettingRepository : syncUserSetting() - Update to local data source")
localUserSettingDataSource.updateUserSetting(
updatedUserSetting,
object : Callback<Unit> {
override fun onSuccess(value: Unit) {
if (Thread.interrupted()) {
callback.onFail(CancellationException())
return
}
Logger.d("UserSettingRepository : syncUserSetting() - Success")
callback.onSuccess(updatedUserSetting)
}
override fun onFail(throwable: Throwable) {
Logger.d("UserSettingRepository : syncUserSetting() - Failed : $throwable")
callback.onFail(throwable)
}
})
}
override fun onFail(throwable: Throwable) {
Logger.d("UserSettingRepository : syncUserSetting() - Failed : $throwable")
callback.onFail(throwable)
}
})
}
override fun onFail(throwable: Throwable) {
Logger.d("UserSettingRepository : syncUserSetting() - Failed : $throwable")
callback.onFail(throwable)
}
})
}.asDisposable()
}
}
interface UserSettingDataSource {
fun loadUserSetting(userId: String, callback: Callback<UserSetting>)
fun updateUserSetting(userSetting: UserSetting, callback: Callback<Unit>)
}
class LocalUserSettingDataSource : UserSettingDataSource {
override fun loadUserSetting(userId: String, callback: Callback<UserSetting>) {
Thread.sleep(100)
callback.onSuccess(
UserSetting(
userId = userId,
primaryColor = "FFFF0000",
secondaryColor = "FF0000FF"
)
)
}
override fun updateUserSetting(userSetting: UserSetting, callback: Callback<Unit>) {
Thread.sleep(100)
callback.onSuccess(Unit)
}
}
class RemoteUserSettingDataSource : UserSettingDataSource {
override fun loadUserSetting(userId: String, callback: Callback<UserSetting>) {
Thread.sleep(200)
callback.onSuccess(
UserSetting(
userId = userId,
primaryColor = "FFFF0000",
secondaryColor = "FF00FF00"
)
)
}
override fun updateUserSetting(userSetting: UserSetting, callback: Callback<Unit>) {
throw UnsupportedOperationException()
}
}
interface Callback<T> {
fun onSuccess(value: T)
fun onFail(throwable: Throwable)
}
data class UserSetting(
val userId: String,
val primaryColor: String,
val secondaryColor: String
) {
fun fold(userSetting: UserSetting): UserSetting {
// Local user setting could have more properties.
return UserSetting(userId, primaryColor, secondaryColor)
}
}
// Utilities for testing
object Logger {
fun d(message: String) {
println("${Thread.currentThread().name.padEnd(40)}\t$message")
}
}
interface Disposable {
fun dispose()
}
class FutureDisposable(private val future: Future<*>) : Disposable {
override fun dispose() {
if (future.isCancelled.not()) {
future.cancel(true)
}
}
}
fun Future<*>.asDisposable(): Disposable {
return FutureDisposable(this)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment