This is a straight forward question but I cannot find an answer. Is there a way to be notified when a lateinit var has been initialised in Kotlin?
I know I can check if it has been initialised with this::coolvar.isInitialized but this is not the same.
Thank you
lateinit var works well only in the simplest cases when uninitialized value doesn't make sense in the context of the app, such as dependency injection or late initialization in onCreate().
What you need is a property delegate with a particular behavior. Take a look at Delegates.observable :
var coolvar by Delegates.observable("initial value") { _, old, new ->
println("coolvar has been updated")
}
If you are using Kotlin coroutines you can handle it with delay() function.
for complex scenarios. not like a direct one if the value changes call this function.
Example:
private var lateinit extractFilesThread: Deferred<Unit>
private fun init() {
GlobalScope.async {loadData()}
GlobalScope.async {doSomeWork()}
}
private suspend fun loadData() {
extractFilesThread = GlobalScope.async {
initialFilesManager.extractDataIfNeeded()
}
}
private suspend fun doSomeWork() {
GlobalScope.async {
waitExtractFilesThread()
startThisWork()
}
}
private suspend fun waitExtractFilesThread() {
while (!::extractFilesThread.isInitialized) {
delay(HALF_SECOND_IN_MILI)
}
extractFilesThread.await()
}
so here you want to call startThisWork() after extractFilesThread Finished
and you can't use await() only if it's already initialized, so we use delay() function, It's a suspend function so it won't block other function executions.
Related
Context:
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
data class DatabaseData(
val a: String,
val b: String
)
interface DatabaseFetcher {
suspend fun get(): DatabaseData
}
class MyClass(
private val databaseFetcher: DatabaseFetcher
) {
suspend fun a() = coroutineScope {
val a = async { databaseFetcher.get().a }
//imagine more async{}'s here
a //imagine a gets computed in regards to the other async{}'s as well
}
suspend fun b() = databaseFetcher.get().b
}
class MyController(private val databaseFetcher: DatabaseFetcher) {
suspend fun someFun() = coroutineScope {
// the reduced example doesn't need a coroutineScope of course, imagine some async{} here
MyClass(databaseFetcher)
}
}
I am trying to call databaseFetcher.get() only once if either a() or b() are called on MyClass. Basically a lazy future that gets started when either a() or b() is called, but with coroutines.
What I have tried so far:
Can't use by lazy{} as the coroutineScope matters here and I can't use withContext(Dispatchers.IO) as I use a custom Context (multithreading, Spring request scoped data etc.) - passing my context in here seems awkward (would it be bad practice?)
I can't pass an async(start = CoroutineStart.LAZY) when constructing MyClass as it would block indefinitely if the Deferred<T> is never await()ed on, which may happen when neither a() or b() is called. It also blocks indefinitely because the corresponding coroutineScope is constructed when MyClass is constructed which would block as a() and b() are called later after MyClass has been fully constructed because (as I understand) a coroutineScope is only unblocked when all its children are done, which doesn't hold true for a lazy async thats awaited outside the curent scope
Using a wider coroutine context may leak when the lazy async is never awaited - is this true? I couldn't find much about this
This is being done in the context of GraphQL wherein either a b or both can be selected. There are boilerplatey solutions to this but as I am still learning about coroutines I wondered if there is an elegant solution to this which I don't see yet. The CoroutineStart.LAZY issue really caught me by surprise :)
I have found a solution for this:
fun <T : () -> U, U> T.memoized(): suspend () -> Deferred<U> {
val self = this
val deferred: CompletableDeferred<U> = CompletableDeferred()
val started = AtomicBoolean(false)
return suspend {
if (started.compareAndExchange(false, true)) {
deferred
} else {
coroutineScope {
async {
deferred.complete(self())
deferred.await()
}
}
}
}
}
Any () -> T function (basically any function with captured arguments) can be .memoized(). Whatever callee first calls the returned suspend fun will be used to start the Deferred<U> while allowing said callee to block whenever he sees fit:
val expensive = { someExpensiveFun(param, param2 }.memoize();
withContext(Dispatchers.IO) { // or some other context
val a = expensive()
val b = expensive()
a.await()
b.await()
}
I want to implement a simple thread-safe Buffer, using Kotlin Coroutines, because coroutines are already used within the project.
The buffer will be used both in multi-thread and single-thread contexts, so having suspend fun getMostRecentData() doesn't seem very reasonable (see code below).
This is what I have so far. The fact that I have to write all that code to lock the mutex makes me wonder if I'm doing something wrong.
Anyway here's the code:
class SafeBuffer(
private val dispatcher: CoroutineDispatcher,
private val bufferSize: Int
) {
private val buffer = LinkedList<MyDataType>()
private val mutex = Mutex()
val size: Int
get() = buffer.size
// First approach: make a suspend fun
// Not great because I will need a runBlocking{} statement somewhere, every time I want to access the buffer
suspend fun getMostRecentData() : MyDataType? {
mutex.withLock {
return if (buffer.isEmpty()) null else buffer.last
}
}
// Second approach: use a runBlocking block inside the function
// Seems like it is missing the purpose of coroutines, and I'm not
// sure it is actually thread safe if other context is used somehow?
fun getMostRecentData() : MyDataType? {
runBlocking(dispatcher) {
mutex.withLock {
return if (buffer.isEmpty()) null else buffer.last
}
}
}
/**** More code ****/
(...)
}
So what's the most idiomatic/elegant way of achieving this?
Expanding on my comment, I think it would be idiomatic to have the buffer class only expose a suspend fun, as the consumer of the class would be responsible for figuring out how they want to use it (via runBlocking or from another coroutine). If you see this use case coming up a lot, an idiomatic approach may be to have an extension function on SafeBuffer to offer this functionality.
Extension functions are used all over the place in the coroutines API. In your code example, even Mutex.withLock is defined as an extension function.
class SafeBuffer(...) {
private val buffer = LinkedList<MyDataType>()
private val mutex = Mutex()
suspend fun getMostRecentData() : MyDataType? =
mutex.withLock {
if (buffer.isEmpty()) null else buffer.last
}
}
fun SafeBuffer.getMostRecentDataBlocking(): MyDataType? =
runBlocking {
getMostRecentData()
}
I'm trying to unit test my viewmodel:
private val loginRepository: LoginRepository = LoginRepository()
private val _loginSuccess = MutableLiveData<Resource<String>>()
val loginSuccess : LiveData<Resource<String>>
get() = _loginSuccess
fun login(credentials : RequestLogin){
_loginSuccess.value = Resource.loading()
viewModelScope.launch {
_loginSuccess.postValue(loginRepository.login(credentials))
}
With this:
#Test
fun login_success(){
val loginRequest = RequestLogin("username", "test")
val app:Application = ApplicationProvider.getApplicationContext()
PreferencesHelper.init(app)
val viewModel = LoginViewModel(app)
viewModel.loginSuccess.observeForever(dataObserver)
runBlocking {
viewModel.login(loginRequest)
assertEquals(viewModel.loginSuccess.getOrAwaitValue(), Resource.success("OK"))
}
viewModel.loginSuccess.removeObserver(dataObserver)
}
But everytime i'm getting just the first value of the liveData object Resource.loading() instead of the one obtained with the postValue method.
How can i ignore the result of the first liveData update and just get the final one?
runBlocking executes and waits for completion for the block you pass to it, in this case it is
viewModel.login(loginRequest)
assertEquals(viewModel.loginSuccess.getOrAwaitValue(), Resource.success("OK"))
But this code does not have any suspend calls, so runBlocking does not have any effect here. In particular it does not affect the viewModelScope.launch call.
There are a couple of ways to test this code. I would suggest using kotlinx-coroutines-test library. It provides TestCoroutineDispatcher which is very convenient in this case.
viewModelScope uses Dispatchers.Main dispatcher by default, so you need to replace it with TestCoroutineDispatcher. E.g. you can create a simple test rule:
class CoroutineTestRule(val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()) : TestWatcher() {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(dispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
Dispatchers.resetMain()
}
}
Then apply it to your test:
#get:Rule
var coroutineTestRule: CoroutineTestRule = CoroutineTestRule()
And use it like this
#Test
fun login_success(){
...
viewModel.login(loginRequest)
coroutineTestRule.dispatcher.advanceUntilIdle()
assertEquals(viewModel.loginSuccess.getOrAwaitValue(), Resource.success("OK"))
...
}
And here is a bit of how it works:
CoroutineTestRule replaces Dispatcher.Main with the CoroutineTestRule.dispatcher
Your viewmodel launches a login job, using viewModelScope, which uses the same CoroutineTestRule.dispatcher
coroutineTestRule.dispatcher.advanceUntilIdle() makes the dispatcher to execute all outstanding tasks, so it will execute all coroutines, which are using this dispatcher and are ready to be executed.
There is also very convenient advanceTimeBy method on TestCoroutineDispatcher which allows you to fast-forward and skip e.g. delay calls.
I have the following class:
class SdkWrapper(private val sdk: Sdk) {
private var inited = false
suspend fun doSomething() = withContext(Dispatchers.IO) {
if (inited.not()) init()
useSdk()
}
private fun init() {
// takes a long time
sdk.init()
inited = true
}
// has to be done asynchronously
// sdk.init() has to have been called before using this
private fun useSdk() {
}
}
class Sdk {
// must only be done once
fun init() {}
}
Before I can do useSdk(), I must call sdk.init(), but sdk.init() must only be called once, not more.
With my current solution, if doSomething is called twice quickly (the second time happening while sdk.init() still running), I would call sdk.init() twice, because inited: Boolean is still false.
If I move the assignment of inited up like:
private fun init() {
inited = true
sdk.init()
}
and doSomething() is called twice rapidly, the second call would use the sdk before its' init() has been done.
I tried to solve this with:
suspend fun doSomething() = synchronized(this){
withContext(Dispatchers.IO) {
if (inited.not()) init()
useSdk()
}
}
but receive an error in IntelliJ:
the withContext suspension point is inside a critical section
I assume that synchronized wouldn't work here anyway, because we move off the main thread and doSomething() is completed while the withContext block is still running?
How can I solve the problem at hand which basically is: doSomething() should only run once at a time?
Instead of synchronized {...} you can use a Mutex:
class SdkWrapper(private val sdk: Sdk) {
...
private val mutex = Mutex()
suspend fun doSomething() = mutex.withLock {
withContext(Dispatchers.IO) {
if (inited.not()) init()
useSdk()
}
}
...
}
You can take a look at the official documentation about Coroutines and mutual exclusion here.
The Code A is from the project architecture-samples, you can see it here.
The updateTasksFromRemoteDataSource() is suspend function, so it maybe run asynchronously.
When I call the function getTasks(forceUpdate: Boolean) with the paramter True, I'm afraid that return tasksLocalDataSource.getTasks() will be fired before updateTasksFromRemoteDataSource().
I don't know if the Code B can guarantee return tasksLocalDataSource.getTasks() will be fired after updateTasksFromRemoteDataSource().
Code A
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {
override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
// Set app as busy while this function executes.
wrapEspressoIdlingResource {
if (forceUpdate) {
try {
updateTasksFromRemoteDataSource()
} catch (ex: Exception) {
return Result.Error(ex)
}
}
return tasksLocalDataSource.getTasks()
}
}
private suspend fun updateTasksFromRemoteDataSource() {
val remoteTasks = tasksRemoteDataSource.getTasks()
if (remoteTasks is Success) {
// Real apps might want to do a proper sync, deleting, modifying or adding each task.
tasksLocalDataSource.deleteAllTasks()
remoteTasks.data.forEach { task ->
tasksLocalDataSource.saveTask(task)
}
} else if (remoteTasks is Result.Error) {
throw remoteTasks.exception
}
}
...
}
Code B
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {
override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
// Set app as busy while this function executes.
wrapEspressoIdlingResource {
coroutineScope {
if (forceUpdate) {
try {
updateTasksFromRemoteDataSource()
} catch (ex: Exception) {
return Result.Error(ex)
}
}
}
return tasksLocalDataSource.getTasks()
}
}
...
}
Added Content
To Tenfour04: Thanks!
If somebody implement updateTasksFromRemoteDataSource() with lauch just like Code C, are you sure the Code C is return tasksLocalDataSource.getTasks() will be fired after updateTasksFromRemoteDataSource() when I call the function getTasks(forceUpdate: Boolean) with the paramter True?
Code C
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {
override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
// Set app as busy while this function executes.
wrapEspressoIdlingResource {
if (forceUpdate) {
try {
updateTasksFromRemoteDataSource()
} catch (ex: Exception) {
return Result.Error(ex)
}
}
return tasksLocalDataSource.getTasks()
}
}
private suspend fun updateTasksFromRemoteDataSource() {
val remoteTasks = tasksRemoteDataSource.getTasks()
if (remoteTasks is Success) {
// Real apps might want to do a proper sync, deleting, modifying or adding each task.
tasksLocalDataSource.deleteAllTasks()
launch { //I suppose that launch can be fired
remoteTasks.data.forEach { task ->
tasksLocalDataSource.saveTask(task)
}
}
} else if (remoteTasks is Result.Error) {
throw remoteTasks.exception
}
}
}
New Added Content
To Joffrey: Thanks!
I think that the Code D can be compiled.
In this case, when forceUpdate is true, tasksLocalDataSource.getTasks() maybe be run before updateTasksFromRemoteDataSource() is done.
Code D
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
private val myCoroutineScope: CoroutineScope
) : TasksRepository {
override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
// Set app as busy while this function executes.
wrapEspressoIdlingResource {
if (forceUpdate) {
try {
updateTasksFromRemoteDataSource(myCoroutineScope)
} catch (ex: Exception) {
return Result.Error(ex)
}
}
return tasksLocalDataSource.getTasks()
}
}
private suspend fun updateTasksFromRemoteDataSource(myCoroutineScope: CoroutineScope) {
val remoteTasks = tasksRemoteDataSource.getTasks()
if (remoteTasks is Success) {
// Real apps might want to do a proper sync, deleting, modifying or adding each task.
tasksLocalDataSource.deleteAllTasks()
myCoroutineScope.launch {
remoteTasks.data.forEach { task ->
tasksLocalDataSource.saveTask(task)
}
}
} else if (remoteTasks is Result.Error) {
throw remoteTasks.exception
}
}
...
}
suspend functions look like regular functions from the call site's point of view because they execute sequentially just like regular synchronous functions.
What I mean by this is that the instructions following a plain call to a suspend function do not execute until the called function completes its execution.
This means that code A is fine (when forceUpdate is true, tasksLocalDataSource.getTasks() will never run before updateTasksFromRemoteDataSource() is done), and the coroutineScope in code B is unnecessary.
Now regarding code C, structured concurrency is here to save you.
People simply cannot call launch without a CoroutineScope receiver.
Since TaskRepository doesn't extend CoroutineScope, the code C as-is will not compile.
There are 2 ways to make this compile though:
Using GlobalScope.launch {}: this will cause the problem you expect, indeed. The body of such a launch will be run asynchronously and independently of the caller. updateTasksFromRemoteDataSource can in this case return before the launch's body is done. The only way to control this is to use .join() on the Job returned by the call to launch (which waits until it's done). This is why it is usually not recommended to use the GlobalScope, because it can "leak" coroutines.
wrapping calls to launch in a coroutineScope {...} inside updateTasksFromRemoteDataSource. This will ensure that all coroutines launched within the coroutineScope block are actually finished before the coroutineScope call completes. Note that everything that's inside the coroutineScope block may very well run concurrently, though, depending on how launch/async are used, but this is the whole point of using launch in the first place, isn't it?
Now with Code D, my answer for code C sort of still holds. Whether you pass a scope or use the GlobalScope, you're effectively creating coroutines with a bigger lifecycle than the suspending function that starts them.
Therefore, it does create the problem you fear.
But why would you pass a CoroutineScope if you don't want implementers to launch long lived coroutines in the provided scope?
Assuming you don't do that, it's unlikely that a developer would use the GlobalScope (or any scope) to do this. It's generally bad style to create long-lived coroutines from a suspending function. If your function is suspending, callers usually expect that when it completes, it has actually done its work.