Last active
February 14, 2022 15:06
-
-
Save myungpyo/3528bb2ec7088cb2e81657b7661cef6c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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